mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
Adds artifacts.cjs with canonical .planning/ root file names, W019 warning in gsd-health that flags unrecognized .md files at the .planning/ root, and templates/README.md as the authoritative artifact index for agents and humans. Closes #2448
This commit is contained in:
@@ -253,6 +253,7 @@
|
||||
"workstream-flag.md"
|
||||
],
|
||||
"cli_modules": [
|
||||
"artifacts.cjs",
|
||||
"audit.cjs",
|
||||
"commands.cjs",
|
||||
"config-schema.cjs",
|
||||
|
||||
@@ -350,12 +350,13 @@ The `gsd-planner` agent is decomposed into a core agent plus reference modules t
|
||||
|
||||
---
|
||||
|
||||
## CLI Modules (25 shipped)
|
||||
## CLI Modules (26 shipped)
|
||||
|
||||
Full listing: `get-shit-done/bin/lib/*.cjs`.
|
||||
|
||||
| Module | Responsibility |
|
||||
|--------|----------------|
|
||||
| `artifacts.cjs` | Canonical artifact registry — known `.planning/` root file names; used by `gsd-health` W019 lint |
|
||||
| `audit.cjs` | Audit dispatch, audit open sessions, audit storage helpers |
|
||||
| `commands.cjs` | Misc CLI commands (slug, timestamp, todos, scaffolding, stats) |
|
||||
| `config-schema.cjs` | Single source of truth for `VALID_CONFIG_KEYS` and dynamic key patterns; imported by both the validator and the config-schema-docs parity test |
|
||||
|
||||
52
get-shit-done/bin/lib/artifacts.cjs
Normal file
52
get-shit-done/bin/lib/artifacts.cjs
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Canonical GSD artifact registry.
|
||||
*
|
||||
* Enumerates the file names that gsd workflows officially produce at the
|
||||
* .planning/ root level. Used by gsd-health (W019) to flag unrecognized files
|
||||
* so stale or misnamed artifacts don't silently mislead agents or reviewers.
|
||||
*
|
||||
* Add entries here whenever a new workflow produces a .planning/ root file.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Exact-match canonical file names at .planning/ root
|
||||
const CANONICAL_EXACT = new Set([
|
||||
'PROJECT.md',
|
||||
'ROADMAP.md',
|
||||
'STATE.md',
|
||||
'REQUIREMENTS.md',
|
||||
'MILESTONES.md',
|
||||
'BACKLOG.md',
|
||||
'LEARNINGS.md',
|
||||
'THREADS.md',
|
||||
'config.json',
|
||||
'CLAUDE.md',
|
||||
]);
|
||||
|
||||
// Pattern-match canonical file names (regex tests on the basename)
|
||||
// Each pattern includes the name of the workflow that produces it as a comment.
|
||||
const CANONICAL_PATTERNS = [
|
||||
/^v\d+\.\d+(?:\.\d+)?-MILESTONE-AUDIT\.md$/i, // gsd-complete-milestone (pre-archive)
|
||||
/^v\d+\.\d+(?:\.\d+)?-.*\.md$/i, // other version-stamped planning docs
|
||||
];
|
||||
|
||||
/**
|
||||
* Return true if `filename` (basename only, no path) matches a canonical
|
||||
* .planning/ root artifact — either an exact name or a known pattern.
|
||||
*
|
||||
* @param {string} filename - Basename of the file (e.g. "STATE.md")
|
||||
*/
|
||||
function isCanonicalPlanningFile(filename) {
|
||||
if (CANONICAL_EXACT.has(filename)) return true;
|
||||
for (const pattern of CANONICAL_PATTERNS) {
|
||||
if (pattern.test(filename)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
CANONICAL_EXACT,
|
||||
CANONICAL_PATTERNS,
|
||||
isCanonicalPlanningFile,
|
||||
};
|
||||
@@ -903,6 +903,22 @@ function cmdValidateHealth(cwd, options, raw) {
|
||||
}
|
||||
} catch { /* intentionally empty — milestone sync check is advisory */ }
|
||||
|
||||
// ─── Check 13: Unrecognized .planning/ root files (W019) ──────────────────
|
||||
try {
|
||||
const { isCanonicalPlanningFile } = require('./artifacts.cjs');
|
||||
const entries = fs.readdirSync(planBase, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile()) continue;
|
||||
if (!entry.name.endsWith('.md')) continue;
|
||||
if (!isCanonicalPlanningFile(entry.name)) {
|
||||
addIssue('warning', 'W019',
|
||||
`Unrecognized .planning/ file: ${entry.name} — not a canonical GSD artifact`,
|
||||
'Move to .planning/milestones/ archive subdir or delete if stale. See templates/README.md for the canonical artifact list.',
|
||||
false);
|
||||
}
|
||||
}
|
||||
} catch { /* artifact check is advisory — skip on error */ }
|
||||
|
||||
// ─── Perform repairs if requested ─────────────────────────────────────────
|
||||
const repairActions = [];
|
||||
if (options.repair && repairs.length > 0) {
|
||||
|
||||
76
get-shit-done/templates/README.md
Normal file
76
get-shit-done/templates/README.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# GSD Canonical Artifact Registry
|
||||
|
||||
This directory contains the template files for every artifact that GSD workflows officially produce. The table below is the authoritative index: **if a `.planning/` root file is not listed here, `gsd-health` will flag it as W019** (unrecognized artifact).
|
||||
|
||||
Agents should query this file before treating a `.planning/` file as authoritative. If the file name does not appear below, it is not a canonical GSD artifact.
|
||||
|
||||
---
|
||||
|
||||
## `.planning/` Root Artifacts
|
||||
|
||||
These files live directly at `.planning/` — not inside phase subdirectories.
|
||||
|
||||
| File | Template | Produced by | Purpose |
|
||||
|------|----------|-------------|---------|
|
||||
| `PROJECT.md` | `project.md` | `/gsd-new-project` | Project identity, goals, requirements summary |
|
||||
| `ROADMAP.md` | `roadmap.md` | `/gsd-new-milestone`, `/gsd-new-project` | Phase plan with milestones and progress tracking |
|
||||
| `STATE.md` | `state.md` | `/gsd-new-project`, `/gsd-health --repair` | Current session state, active phase, last activity |
|
||||
| `REQUIREMENTS.md` | `requirements.md` | `/gsd-new-milestone` | Functional requirements with traceability |
|
||||
| `MILESTONES.md` | `milestone.md` | `/gsd-complete-milestone` | Log of completed milestones with accomplishments |
|
||||
| `BACKLOG.md` | *(inline)* | `/gsd-add-backlog` | Pending ideas and deferred work |
|
||||
| `LEARNINGS.md` | *(inline)* | `/gsd-extract-learnings`, `/gsd-execute-phase` | Phase retrospective learnings for future plans |
|
||||
| `THREADS.md` | *(inline)* | `/gsd-thread` | Persistent discussion threads |
|
||||
| `config.json` | `config.json` | `/gsd-new-project`, `/gsd-health --repair` | Project-specific GSD configuration |
|
||||
| `CLAUDE.md` | `claude-md.md` | `/gsd-profile` | Auto-assembled Claude Code context file |
|
||||
|
||||
### Version-stamped artifacts (pattern: `vX.Y-*.md`)
|
||||
|
||||
| Pattern | Produced by | Purpose |
|
||||
|---------|-------------|---------|
|
||||
| `vX.Y-MILESTONE-AUDIT.md` | `/gsd-audit-milestone` | Milestone audit report before archiving |
|
||||
|
||||
These files are archived to `.planning/milestones/` by `/gsd-complete-milestone`. Finding them at the `.planning/` root after completion indicates the archive step was skipped.
|
||||
|
||||
---
|
||||
|
||||
## Phase Subdirectory Artifacts (`.planning/phases/NN-name/`)
|
||||
|
||||
These files live inside a phase directory. They are NOT checked by W019 (which only inspects the `.planning/` root).
|
||||
|
||||
| File Pattern | Template | Produced by | Purpose |
|
||||
|-------------|----------|-------------|---------|
|
||||
| `NN-MM-PLAN.md` | `phase-prompt.md` | `/gsd-plan-phase` | Executable implementation plan |
|
||||
| `NN-MM-SUMMARY.md` | `summary.md` | `/gsd-execute-phase` | Post-execution summary with learnings |
|
||||
| `NN-CONTEXT.md` | `context.md` | `/gsd-discuss-phase` | Scoped discussion decisions for the phase |
|
||||
| `NN-RESEARCH.md` | `research.md` | `/gsd-research-phase`, `/gsd-plan-phase` | Technical research for the phase |
|
||||
| `NN-VALIDATION.md` | `VALIDATION.md` | `/gsd-research-phase` (Nyquist) | Validation architecture (Nyquist method) |
|
||||
| `NN-UAT.md` | `UAT.md` | `/gsd-validate-phase` | User acceptance test results |
|
||||
| `NN-PATTERNS.md` | *(inline)* | `/gsd-plan-phase` (pattern mapper) | Analog file mapping for the phase |
|
||||
| `NN-UI-SPEC.md` | `UI-SPEC.md` | `/gsd-ui-phase` | UI design contract |
|
||||
| `NN-SECURITY.md` | `SECURITY.md` | `/gsd-secure-phase` | Security threat model |
|
||||
| `NN-AI-SPEC.md` | `AI-SPEC.md` | `/gsd-ai-integration-phase` | AI integration spec with eval strategy |
|
||||
| `NN-DEBUG.md` | `DEBUG.md` | `/gsd-debug` | Debug session log |
|
||||
| `NN-REVIEWS.md` | *(inline)* | `/gsd-review` | Cross-AI review feedback |
|
||||
|
||||
---
|
||||
|
||||
## Milestone Archive (`.planning/milestones/`)
|
||||
|
||||
Files archived by `/gsd-complete-milestone`. These are never checked by W019.
|
||||
|
||||
| File Pattern | Source |
|
||||
|-------------|--------|
|
||||
| `vX.Y-ROADMAP.md` | Snapshot of ROADMAP.md at milestone close |
|
||||
| `vX.Y-REQUIREMENTS.md` | Snapshot of REQUIREMENTS.md at milestone close |
|
||||
| `vX.Y-MILESTONE-AUDIT.md` | Moved from `.planning/` root |
|
||||
| `vX.Y-phases/` | Archived phase directories (if `--archive-phases` used) |
|
||||
|
||||
---
|
||||
|
||||
## Adding a New Canonical Artifact
|
||||
|
||||
When a new workflow produces a `.planning/` root file:
|
||||
|
||||
1. Add the file name to `CANONICAL_EXACT` in `get-shit-done/bin/lib/artifacts.cjs`
|
||||
2. Add a row to the **`.planning/` Root Artifacts** table above
|
||||
3. Add the template to `get-shit-done/templates/` if one exists
|
||||
@@ -143,6 +143,7 @@ Report final status.
|
||||
| W008 | warning | config.json: workflow.nyquist_validation absent (defaults to enabled but agents may skip) | Yes |
|
||||
| W009 | warning | Phase has Validation Architecture in RESEARCH.md but no VALIDATION.md | No |
|
||||
| W018 | warning | MILESTONES.md missing entry for archived milestone snapshot | Yes (`--backfill`) |
|
||||
| W019 | warning | Unrecognized .planning/ root file — not a canonical GSD artifact | No |
|
||||
| I001 | info | Plan without SUMMARY (may be in progress) | No |
|
||||
|
||||
</error_codes>
|
||||
|
||||
127
tests/enh-2448-artifact-registry.test.cjs
Normal file
127
tests/enh-2448-artifact-registry.test.cjs
Normal file
@@ -0,0 +1,127 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Tests for canonical artifact registry and gsd-health W019 lint (#2448).
|
||||
*/
|
||||
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const os = require('node:os');
|
||||
|
||||
const { isCanonicalPlanningFile, CANONICAL_EXACT } = require('../get-shit-done/bin/lib/artifacts.cjs');
|
||||
const { cmdValidateHealth } = require('../get-shit-done/bin/lib/verify.cjs');
|
||||
|
||||
function makeTempProject(files = {}) {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-2448-'));
|
||||
fs.mkdirSync(path.join(dir, '.planning', 'phases'), { recursive: true });
|
||||
for (const [rel, content] of Object.entries(files)) {
|
||||
const abs = path.join(dir, rel);
|
||||
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
||||
fs.writeFileSync(abs, content, 'utf-8');
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
const BASE_FILES = {
|
||||
'.planning/PROJECT.md': '# P\n\n## What This Is\n\nX\n\n## Core Value\n\nY\n\n## Requirements\n\nZ\n',
|
||||
'.planning/ROADMAP.md': '# Roadmap\n',
|
||||
'.planning/STATE.md': '# State\n',
|
||||
'.planning/config.json': '{}',
|
||||
};
|
||||
|
||||
describe('artifacts.cjs — isCanonicalPlanningFile', () => {
|
||||
test('returns true for all exact canonical names', () => {
|
||||
for (const name of CANONICAL_EXACT) {
|
||||
assert.ok(isCanonicalPlanningFile(name), `Expected ${name} to be canonical`);
|
||||
}
|
||||
});
|
||||
|
||||
test('returns true for version-stamped milestone audit file', () => {
|
||||
assert.ok(isCanonicalPlanningFile('v1.0-MILESTONE-AUDIT.md'));
|
||||
assert.ok(isCanonicalPlanningFile('v2.3.1-MILESTONE-AUDIT.md'));
|
||||
});
|
||||
|
||||
test('returns false for clearly non-canonical names', () => {
|
||||
assert.strictEqual(isCanonicalPlanningFile('MY-NOTES.md'), false);
|
||||
assert.strictEqual(isCanonicalPlanningFile('scratch.md'), false);
|
||||
assert.strictEqual(isCanonicalPlanningFile('random-output.md'), false);
|
||||
});
|
||||
|
||||
test('returns false for phase-level artifacts at the root (they belong in phases/)', () => {
|
||||
assert.strictEqual(isCanonicalPlanningFile('01-CONTEXT.md'), false);
|
||||
assert.strictEqual(isCanonicalPlanningFile('01-01-PLAN.md'), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('gsd-health W019 — unrecognized .planning/ root files', () => {
|
||||
test('W019 fires for a non-canonical .md file at .planning/ root', () => {
|
||||
const dir = makeTempProject({
|
||||
...BASE_FILES,
|
||||
'.planning/MY-NOTES.md': '# notes\n',
|
||||
});
|
||||
|
||||
const result = cmdValidateHealth(dir, { repair: false }, false);
|
||||
|
||||
const w019 = result.warnings.find(w => w.code === 'W019');
|
||||
assert.ok(w019, 'W019 should be emitted for unrecognized file');
|
||||
assert.ok(w019.message.includes('MY-NOTES.md'), 'warning should name the file');
|
||||
assert.strictEqual(w019.repairable, false, 'W019 is not auto-repairable');
|
||||
});
|
||||
|
||||
test('no W019 for canonical files', () => {
|
||||
const dir = makeTempProject({ ...BASE_FILES });
|
||||
|
||||
const result = cmdValidateHealth(dir, { repair: false }, false);
|
||||
|
||||
const w019 = result.warnings.find(w => w.code === 'W019');
|
||||
assert.strictEqual(w019, undefined, 'no W019 for canonical files');
|
||||
});
|
||||
|
||||
test('no W019 for phase subdirectory files (only root is checked)', () => {
|
||||
const dir = makeTempProject({
|
||||
...BASE_FILES,
|
||||
'.planning/phases/01-foundation/01-01-PLAN.md': '---\nphase: "1"\n---\n',
|
||||
});
|
||||
|
||||
const result = cmdValidateHealth(dir, { repair: false }, false);
|
||||
|
||||
const w019 = result.warnings.find(w => w.code === 'W019');
|
||||
assert.strictEqual(w019, undefined, 'phase subdir files not flagged by W019');
|
||||
});
|
||||
|
||||
test('no W019 for version-stamped files like vX.Y-MILESTONE-AUDIT.md', () => {
|
||||
const dir = makeTempProject({
|
||||
...BASE_FILES,
|
||||
'.planning/v1.0-MILESTONE-AUDIT.md': '# Audit\n',
|
||||
});
|
||||
|
||||
const result = cmdValidateHealth(dir, { repair: false }, false);
|
||||
|
||||
const w019 = result.warnings.find(w => w.code === 'W019');
|
||||
assert.strictEqual(w019, undefined, 'version-stamped audit file is canonical');
|
||||
});
|
||||
|
||||
test('multiple unrecognized files produce multiple W019 warnings', () => {
|
||||
const dir = makeTempProject({
|
||||
...BASE_FILES,
|
||||
'.planning/scratch.md': '# scratch\n',
|
||||
'.planning/temp-notes.md': '# temp\n',
|
||||
});
|
||||
|
||||
const result = cmdValidateHealth(dir, { repair: false }, false);
|
||||
|
||||
const w019s = result.warnings.filter(w => w.code === 'W019');
|
||||
assert.strictEqual(w019s.length, 2, 'one W019 per unrecognized file');
|
||||
});
|
||||
|
||||
test('templates/README.md exists and documents W019', () => {
|
||||
const readme = fs.readFileSync(
|
||||
path.join(__dirname, '../get-shit-done/templates/README.md'), 'utf-8'
|
||||
);
|
||||
assert.ok(readme.includes('W019'), 'README.md documents W019');
|
||||
assert.ok(readme.includes('artifacts.cjs'), 'README.md references artifacts.cjs for adding new artifacts');
|
||||
assert.ok(readme.includes('PROJECT.md'), 'README.md lists PROJECT.md as canonical');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user