agent-method 1.5.3 → 1.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +197 -57
- package/bin/wwa.js +35 -9
- package/docs/internal/doc-tokens.yaml +452 -0
- package/docs/internal/feature-registry.yaml +13 -1
- package/lib/cli/casestudy.js +691 -0
- package/lib/cli/check.js +71 -71
- package/lib/cli/close.js +446 -0
- package/lib/cli/completion.js +639 -0
- package/lib/cli/digest.js +66 -0
- package/lib/cli/docs.js +207 -0
- package/lib/cli/helpers.js +49 -2
- package/lib/cli/implement.js +159 -0
- package/lib/cli/init.js +25 -6
- package/lib/cli/plan.js +128 -0
- package/lib/cli/refine.js +202 -202
- package/lib/cli/review.js +68 -0
- package/lib/cli/scan.js +28 -28
- package/lib/cli/status.js +61 -61
- package/lib/cli/upgrade.js +150 -147
- package/lib/init.js +478 -296
- package/package.json +12 -4
- package/templates/README.md +73 -25
- package/templates/entry-points/.cursorrules +143 -14
- package/templates/entry-points/AGENT.md +143 -14
- package/templates/entry-points/CLAUDE.md +143 -14
- package/templates/extensions/analytical-system.md +1 -1
- package/templates/extensions/code-project.md +1 -1
- package/templates/extensions/data-exploration.md +1 -1
- package/templates/full/.context/BASE.md +33 -0
- package/templates/full/.context/METHODOLOGY.md +62 -5
- package/templates/full/.cursorrules +128 -18
- package/templates/full/AGENT.md +128 -18
- package/templates/full/CLAUDE.md +128 -18
- package/templates/full/Management/DIGEST.md +23 -0
- package/templates/full/Management/STATUS.md +46 -0
- package/templates/full/PROJECT.md +34 -0
- package/templates/full/Reviews/INDEX.md +41 -0
- package/templates/full/Reviews/backlog.md +52 -0
- package/templates/full/Reviews/plan.md +43 -0
- package/templates/full/Reviews/project.md +41 -0
- package/templates/full/Reviews/requirements.md +42 -0
- package/templates/full/Reviews/roadmap.md +41 -0
- package/templates/full/Reviews/state.md +56 -0
- package/templates/full/SESSION-LOG.md +29 -0
- package/templates/full/SUMMARY.md +7 -4
- package/templates/full/agentWorkflows/INDEX.md +42 -0
- package/templates/full/agentWorkflows/observations.md +65 -0
- package/templates/full/agentWorkflows/patterns.md +68 -0
- package/templates/full/agentWorkflows/sessions.md +92 -0
- package/templates/full/intro/README.md +39 -0
- package/templates/starter/.context/BASE.md +35 -0
- package/templates/starter/.context/METHODOLOGY.md +59 -5
- package/templates/starter/.cursorrules +135 -13
- package/templates/starter/AGENT.md +135 -13
- package/templates/starter/CLAUDE.md +135 -13
- package/templates/starter/Management/DIGEST.md +23 -0
- package/templates/starter/Management/STATUS.md +46 -0
- package/templates/starter/PROJECT.md +34 -0
- package/templates/starter/Reviews/INDEX.md +75 -0
- package/templates/starter/SESSION-LOG.md +29 -0
- package/templates/starter/SUMMARY.md +27 -0
- package/templates/starter/agentWorkflows/INDEX.md +61 -0
- package/templates/starter/intro/README.md +37 -0
- package/templates/full/docs/index.md +0 -46
package/lib/cli/check.js
CHANGED
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
/** wwa check — validate entry point and project setup. */
|
|
2
|
-
|
|
3
|
-
import { dirname, resolve } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
findEntryPoint,
|
|
6
|
-
resolveProjectType,
|
|
7
|
-
getPipeline,
|
|
8
|
-
loadRegistryData,
|
|
9
|
-
} from "./helpers.js";
|
|
10
|
-
|
|
11
|
-
export function register(program) {
|
|
12
|
-
program
|
|
13
|
-
.command("check [entry-point]")
|
|
14
|
-
.description("Validate your entry point and project setup")
|
|
15
|
-
.option(
|
|
16
|
-
"-p, --project-type <type>",
|
|
17
|
-
"Project type: code, context, data, mix, general (auto-detected if omitted)"
|
|
18
|
-
)
|
|
19
|
-
.option("--registry <path>", "Path to feature-registry.yaml")
|
|
20
|
-
.option("--json", "Output as JSON")
|
|
21
|
-
.action(async (entryPoint, opts) => {
|
|
22
|
-
const { validateEntryPoint, detectProjectType } = await getPipeline();
|
|
23
|
-
const reg = await loadRegistryData(opts.registry);
|
|
24
|
-
|
|
25
|
-
if (!entryPoint) {
|
|
26
|
-
entryPoint = findEntryPoint(".");
|
|
27
|
-
if (!entryPoint) {
|
|
28
|
-
console.error(
|
|
29
|
-
"No entry point found in current directory " +
|
|
30
|
-
"(looked for CLAUDE.md, .cursorrules, AGENT.md).\n" +
|
|
31
|
-
"Specify a path:
|
|
32
|
-
);
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
let projectType;
|
|
38
|
-
if (opts.projectType) {
|
|
39
|
-
projectType = resolveProjectType(opts.projectType);
|
|
40
|
-
} else {
|
|
41
|
-
const detected = detectProjectType(dirname(resolve(entryPoint)));
|
|
42
|
-
projectType = detected.project_type || "general";
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const result = validateEntryPoint(entryPoint, projectType, reg);
|
|
46
|
-
console.log(`Checking: ${entryPoint} (type: ${projectType})`);
|
|
47
|
-
|
|
48
|
-
if (opts.json) {
|
|
49
|
-
console.log(JSON.stringify(result, null, 2));
|
|
50
|
-
} else {
|
|
51
|
-
const overall = result.valid ? "PASS" : "FAIL";
|
|
52
|
-
console.log(` Overall: ${overall}`);
|
|
53
|
-
for (const [checkId, chk] of Object.entries(result.checks)) {
|
|
54
|
-
const cStatus = chk.pass ? "PASS" : "FAIL";
|
|
55
|
-
let extra = "";
|
|
56
|
-
if (chk.missing && chk.missing.length > 0) {
|
|
57
|
-
extra = ` (missing: ${chk.missing.join(", ")})`;
|
|
58
|
-
}
|
|
59
|
-
console.log(` ${checkId}: ${cStatus}${extra}`);
|
|
60
|
-
}
|
|
61
|
-
if (result.issues && result.issues.length > 0) {
|
|
62
|
-
console.log("\n Issues:");
|
|
63
|
-
for (const issue of result.issues) {
|
|
64
|
-
console.log(
|
|
65
|
-
` [${issue.severity}] ${issue.check}: ${issue.description}`
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
}
|
|
1
|
+
/** wwa check — validate entry point and project setup. */
|
|
2
|
+
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
findEntryPoint,
|
|
6
|
+
resolveProjectType,
|
|
7
|
+
getPipeline,
|
|
8
|
+
loadRegistryData,
|
|
9
|
+
} from "./helpers.js";
|
|
10
|
+
|
|
11
|
+
export function register(program) {
|
|
12
|
+
program
|
|
13
|
+
.command("check [entry-point]")
|
|
14
|
+
.description("Validate your entry point and project setup")
|
|
15
|
+
.option(
|
|
16
|
+
"-p, --project-type <type>",
|
|
17
|
+
"Project type: code, context, data, mix, general (auto-detected if omitted)"
|
|
18
|
+
)
|
|
19
|
+
.option("--registry <path>", "Path to feature-registry.yaml")
|
|
20
|
+
.option("--json", "Output as JSON")
|
|
21
|
+
.action(async (entryPoint, opts) => {
|
|
22
|
+
const { validateEntryPoint, detectProjectType } = await getPipeline();
|
|
23
|
+
const reg = await loadRegistryData(opts.registry);
|
|
24
|
+
|
|
25
|
+
if (!entryPoint) {
|
|
26
|
+
entryPoint = findEntryPoint(".");
|
|
27
|
+
if (!entryPoint) {
|
|
28
|
+
console.error(
|
|
29
|
+
"No entry point found in current directory " +
|
|
30
|
+
"(looked for CLAUDE.md, .cursorrules, AGENT.md).\n" +
|
|
31
|
+
"Specify a path: wwa check path/to/CLAUDE.md"
|
|
32
|
+
);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let projectType;
|
|
38
|
+
if (opts.projectType) {
|
|
39
|
+
projectType = resolveProjectType(opts.projectType);
|
|
40
|
+
} else {
|
|
41
|
+
const detected = detectProjectType(dirname(resolve(entryPoint)));
|
|
42
|
+
projectType = detected.project_type || "general";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const result = validateEntryPoint(entryPoint, projectType, reg);
|
|
46
|
+
console.log(`Checking: ${entryPoint} (type: ${projectType})`);
|
|
47
|
+
|
|
48
|
+
if (opts.json) {
|
|
49
|
+
console.log(JSON.stringify(result, null, 2));
|
|
50
|
+
} else {
|
|
51
|
+
const overall = result.valid ? "PASS" : "FAIL";
|
|
52
|
+
console.log(` Overall: ${overall}`);
|
|
53
|
+
for (const [checkId, chk] of Object.entries(result.checks)) {
|
|
54
|
+
const cStatus = chk.pass ? "PASS" : "FAIL";
|
|
55
|
+
let extra = "";
|
|
56
|
+
if (chk.missing && chk.missing.length > 0) {
|
|
57
|
+
extra = ` (missing: ${chk.missing.join(", ")})`;
|
|
58
|
+
}
|
|
59
|
+
console.log(` ${checkId}: ${cStatus}${extra}`);
|
|
60
|
+
}
|
|
61
|
+
if (result.issues && result.issues.length > 0) {
|
|
62
|
+
console.log("\n Issues:");
|
|
63
|
+
for (const issue of result.issues) {
|
|
64
|
+
console.log(
|
|
65
|
+
` [${issue.severity}] ${issue.check}: ${issue.description}`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
package/lib/cli/close.js
ADDED
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
/** wwa close — session close checklist, cascade reminders, and project snapshot. */
|
|
2
|
+
|
|
3
|
+
import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
4
|
+
import { resolve, join, dirname, relative } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { findEntryPoint, findSessionLog, readMethodVersion, packageRoot, safeWriteFile } from "./helpers.js";
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
|
|
11
|
+
export function register(program) {
|
|
12
|
+
program
|
|
13
|
+
.command("close [directory]")
|
|
14
|
+
.description("Show session close checklist, cascade reminders, and project snapshot")
|
|
15
|
+
.option("--json", "Output as JSON")
|
|
16
|
+
.action(async (directory, opts) => {
|
|
17
|
+
directory = directory || ".";
|
|
18
|
+
const d = resolve(directory);
|
|
19
|
+
|
|
20
|
+
const checklist = [];
|
|
21
|
+
const warnings = [];
|
|
22
|
+
|
|
23
|
+
// 1. Check SESSION-LOG.md exists
|
|
24
|
+
const sessionLog = findSessionLog(directory);
|
|
25
|
+
if (sessionLog) {
|
|
26
|
+
checklist.push({ item: "SESSION-LOG.md exists", ok: true });
|
|
27
|
+
} else {
|
|
28
|
+
checklist.push({ item: "SESSION-LOG.md exists", ok: false });
|
|
29
|
+
warnings.push("No SESSION-LOG.md found — create one to track session metrics");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 2. Check STATE.md exists and has current position
|
|
33
|
+
const statePath = join(d, "STATE.md");
|
|
34
|
+
if (existsSync(statePath)) {
|
|
35
|
+
checklist.push({ item: "STATE.md exists", ok: true });
|
|
36
|
+
} else {
|
|
37
|
+
checklist.push({ item: "STATE.md exists", ok: false });
|
|
38
|
+
warnings.push("No STATE.md found — decisions may be lost between sessions");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 3. Check PLAN.md for incomplete steps
|
|
42
|
+
const planPath = join(d, "PLAN.md");
|
|
43
|
+
if (existsSync(planPath)) {
|
|
44
|
+
const planContent = readFileSync(planPath, "utf-8");
|
|
45
|
+
const todoSteps = (planContent.match(/\|\s*todo\s*\|/gi) || []).length;
|
|
46
|
+
if (todoSteps > 0) {
|
|
47
|
+
checklist.push({
|
|
48
|
+
item: `PLAN.md has ${todoSteps} incomplete step(s)`,
|
|
49
|
+
ok: false,
|
|
50
|
+
});
|
|
51
|
+
} else {
|
|
52
|
+
checklist.push({ item: "PLAN.md — all steps complete", ok: true });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 4. Check Management files
|
|
57
|
+
const digestPath = join(d, "Management", "DIGEST.md");
|
|
58
|
+
if (existsSync(digestPath)) {
|
|
59
|
+
checklist.push({ item: "Management/DIGEST.md exists", ok: true });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 5. Check SUMMARY.md
|
|
63
|
+
const summaryPath = join(d, "SUMMARY.md");
|
|
64
|
+
if (existsSync(summaryPath)) {
|
|
65
|
+
checklist.push({ item: "SUMMARY.md exists for audit entry", ok: true });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Build cascade reminders
|
|
69
|
+
const cascadeReminders = [
|
|
70
|
+
"Append metrics entry to SESSION-LOG.md",
|
|
71
|
+
"Update STATE.md current position",
|
|
72
|
+
"For high-effort tasks: append row to Management/DIGEST.md",
|
|
73
|
+
"If milestone: update Management/STATUS.md",
|
|
74
|
+
"If phase complete: mark ROADMAP.md, write SUMMARY.md entry",
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
// Compute project snapshot — methodology reference data
|
|
78
|
+
const snapshot = await computeProjectSnapshot(d);
|
|
79
|
+
|
|
80
|
+
// Update project doc-tokens.yaml regardless of output mode
|
|
81
|
+
const tokenUpdateResult = await updateProjectTokens(d, snapshot);
|
|
82
|
+
|
|
83
|
+
if (opts.json) {
|
|
84
|
+
console.log(
|
|
85
|
+
JSON.stringify({ checklist, warnings, cascadeReminders, snapshot, tokensUpdated: !!tokenUpdateResult }, null, 2)
|
|
86
|
+
);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log("\n Session Close Checklist\n");
|
|
91
|
+
|
|
92
|
+
for (const item of checklist) {
|
|
93
|
+
const icon = item.ok ? "[x]" : "[ ]";
|
|
94
|
+
console.log(` ${icon} ${item.item}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (warnings.length > 0) {
|
|
98
|
+
console.log("\n Warnings:");
|
|
99
|
+
for (const w of warnings) {
|
|
100
|
+
console.log(` ! ${w}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log("\n Cascade reminders:");
|
|
105
|
+
for (const r of cascadeReminders) {
|
|
106
|
+
console.log(` - ${r}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Print project snapshot
|
|
110
|
+
printSnapshot(snapshot);
|
|
111
|
+
|
|
112
|
+
if (tokenUpdateResult) {
|
|
113
|
+
console.log("\n Updated .context/doc-tokens.yaml with current project metrics.");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log("");
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// Project snapshot — computed at close, chunked for large projects
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
async function computeProjectSnapshot(dir) {
|
|
125
|
+
const snapshot = {
|
|
126
|
+
project: {},
|
|
127
|
+
methodologyFiles: { present: [], missing: [] },
|
|
128
|
+
decisions: { count: 0, recent: [] },
|
|
129
|
+
sessions: { count: 0, latestDate: null },
|
|
130
|
+
features: [],
|
|
131
|
+
registry: null,
|
|
132
|
+
docsMap: null,
|
|
133
|
+
fileStats: {},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// --- Chunk 1: Project identity ---
|
|
137
|
+
const entryPoint = findEntryPoint(dir);
|
|
138
|
+
if (entryPoint) {
|
|
139
|
+
snapshot.project.entryPoint = relative(dir, entryPoint) || entryPoint;
|
|
140
|
+
snapshot.project.methodVersion = readMethodVersion(entryPoint);
|
|
141
|
+
const epContent = readFileSync(entryPoint, "utf-8");
|
|
142
|
+
const tierMatch = epContent.match(/tier:\s*(\S+)/);
|
|
143
|
+
if (tierMatch) snapshot.project.tier = tierMatch[1];
|
|
144
|
+
const modeMatch = epContent.match(/mode:\s*(\S+)/);
|
|
145
|
+
if (modeMatch) snapshot.project.interactionLevel = modeMatch[1];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const projectPath = join(dir, "PROJECT.md");
|
|
149
|
+
if (existsSync(projectPath)) {
|
|
150
|
+
const content = readFileSync(projectPath, "utf-8");
|
|
151
|
+
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
152
|
+
if (titleMatch) snapshot.project.name = titleMatch[1].trim();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const profilePath = join(dir, "PROJECT-PROFILE.md");
|
|
156
|
+
if (existsSync(profilePath)) {
|
|
157
|
+
const content = readFileSync(profilePath, "utf-8");
|
|
158
|
+
const typeMatch = content.match(/project.?type[:\s|]+(\S+)/i);
|
|
159
|
+
if (typeMatch) snapshot.project.type = typeMatch[1].toLowerCase();
|
|
160
|
+
const stageMatch = content.match(/lifecycle.?stage[:\s|]+(\S+)/i);
|
|
161
|
+
if (stageMatch) snapshot.project.lifecycleStage = stageMatch[1].toLowerCase();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// --- Chunk 2: Methodology file inventory ---
|
|
165
|
+
const expectedFiles = [
|
|
166
|
+
"STATE.md", "PLAN.md", "ROADMAP.md", "SUMMARY.md",
|
|
167
|
+
"PROJECT.md", "PROJECT-PROFILE.md", "SESSION-LOG.md",
|
|
168
|
+
".context/BASE.md", ".context/METHODOLOGY.md",
|
|
169
|
+
];
|
|
170
|
+
for (const f of expectedFiles) {
|
|
171
|
+
const p = join(dir, f);
|
|
172
|
+
if (existsSync(p)) {
|
|
173
|
+
const stat = statSync(p);
|
|
174
|
+
const lines = readFileSync(p, "utf-8").split("\n").length;
|
|
175
|
+
snapshot.methodologyFiles.present.push({ file: f, lines, bytes: stat.size });
|
|
176
|
+
// Track files approaching 300-line threshold
|
|
177
|
+
if (lines > 250) {
|
|
178
|
+
snapshot.fileStats[f] = { lines, nearThreshold: lines >= 280 };
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
snapshot.methodologyFiles.missing.push(f);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// --- Chunk 3: Decisions from STATE.md ---
|
|
186
|
+
const statePath = join(dir, "STATE.md");
|
|
187
|
+
if (existsSync(statePath)) {
|
|
188
|
+
const content = readFileSync(statePath, "utf-8");
|
|
189
|
+
const decisionSection = content.match(
|
|
190
|
+
/##\s*Decisions?\s*\n([\s\S]*?)(?=\n##|\n$|$)/i
|
|
191
|
+
);
|
|
192
|
+
if (decisionSection) {
|
|
193
|
+
const rows = decisionSection[1].match(/\|[^|\n]+\|[^|\n]+\|/g) || [];
|
|
194
|
+
const decisions = [];
|
|
195
|
+
for (const row of rows) {
|
|
196
|
+
const cols = row.split("|").map((c) => c.trim()).filter((c) => c && !c.match(/^[-:]+$/));
|
|
197
|
+
if (cols.length >= 2 && !cols[0].toLowerCase().includes("date")) {
|
|
198
|
+
decisions.push({ date: cols[0], decision: cols.slice(1).join(" — ") });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
snapshot.decisions.count = decisions.length;
|
|
202
|
+
snapshot.decisions.recent = decisions.slice(-5); // last 5
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// --- Chunk 4: Session count from SESSION-LOG.md ---
|
|
207
|
+
const sessionLog = findSessionLog(dir);
|
|
208
|
+
if (sessionLog) {
|
|
209
|
+
const content = readFileSync(sessionLog, "utf-8");
|
|
210
|
+
const entries = content.match(/###\s+S\d+/g) || [];
|
|
211
|
+
snapshot.sessions.count = entries.length;
|
|
212
|
+
// Find the latest date
|
|
213
|
+
const dates = content.match(/###\s+S\d+\s*(?:—|--)\s*(\S+)/g) || [];
|
|
214
|
+
if (dates.length > 0) {
|
|
215
|
+
const lastDate = dates[dates.length - 1].match(/(\d{4}-\d{2}-\d{2})/);
|
|
216
|
+
if (lastDate) snapshot.sessions.latestDate = lastDate[1];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// --- Chunk 5: Feature registry (from project .context/ or installed package) ---
|
|
221
|
+
const localRegistry = join(dir, ".context", "feature-registry.yaml");
|
|
222
|
+
const pkgRegistry = join(packageRoot, "docs", "internal", "feature-registry.yaml");
|
|
223
|
+
const registryPath = existsSync(localRegistry) ? localRegistry : (existsSync(pkgRegistry) ? pkgRegistry : null);
|
|
224
|
+
|
|
225
|
+
if (registryPath) {
|
|
226
|
+
try {
|
|
227
|
+
const yaml = (await import("js-yaml")).default;
|
|
228
|
+
const raw = readFileSync(registryPath, "utf-8");
|
|
229
|
+
const parsed = yaml.load(raw);
|
|
230
|
+
if (parsed) {
|
|
231
|
+
snapshot.registry = {
|
|
232
|
+
source: existsSync(localRegistry) ? "project" : "package",
|
|
233
|
+
version: parsed.version || null,
|
|
234
|
+
featureCount: parsed.features ? Object.keys(parsed.features).length : 0,
|
|
235
|
+
workflowCount: parsed.guided_workflows ? parsed.guided_workflows.length : 0,
|
|
236
|
+
};
|
|
237
|
+
// Collect active features from project entry point scoping
|
|
238
|
+
if (entryPoint) {
|
|
239
|
+
const epContent = readFileSync(entryPoint, "utf-8");
|
|
240
|
+
const featureRefs = epContent.match(/[A-Z]{2,5}-\d{2}/g) || [];
|
|
241
|
+
snapshot.features = [...new Set(featureRefs)].sort();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} catch (_) {
|
|
245
|
+
// Registry parse failed — skip silently
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// --- Chunk 6: Docs map status ---
|
|
250
|
+
const docsMapPath = join(dir, ".context", "DOCS-MAP.md");
|
|
251
|
+
if (existsSync(docsMapPath)) {
|
|
252
|
+
const content = readFileSync(docsMapPath, "utf-8");
|
|
253
|
+
const inventoryRows = (content.match(/^\|[^|]+\|[^|]+\|[^|]+\|[^|]+\|$/gm) || [])
|
|
254
|
+
.filter((r) => !r.includes("---") && !r.includes("Path") && !r.includes("Purpose"));
|
|
255
|
+
const mappingRows = (content.match(/^\|[^|]+\|[^|]+\|[^|]+\|$/gm) || [])
|
|
256
|
+
.filter((r) => !r.includes("---") && !r.includes("Project component") && !r.includes("When this"));
|
|
257
|
+
|
|
258
|
+
// Check which docs actually exist on disk
|
|
259
|
+
let docsExist = 0;
|
|
260
|
+
let docsTotal = 0;
|
|
261
|
+
for (const row of inventoryRows) {
|
|
262
|
+
const cols = row.split("|").map((c) => c.trim()).filter((c) => c);
|
|
263
|
+
if (cols.length >= 1) {
|
|
264
|
+
docsTotal++;
|
|
265
|
+
if (existsSync(join(dir, cols[0]))) docsExist++;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
snapshot.docsMap = {
|
|
270
|
+
inventoryCount: docsTotal,
|
|
271
|
+
docsOnDisk: docsExist,
|
|
272
|
+
docsMissing: docsTotal - docsExist,
|
|
273
|
+
componentMappings: mappingRows.length,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return snapshot;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
// Display
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
|
|
284
|
+
function printSnapshot(snapshot) {
|
|
285
|
+
console.log("\n Project Snapshot\n");
|
|
286
|
+
|
|
287
|
+
// Project identity
|
|
288
|
+
if (snapshot.project.name) {
|
|
289
|
+
console.log(` Project: ${snapshot.project.name}`);
|
|
290
|
+
}
|
|
291
|
+
if (snapshot.project.type) {
|
|
292
|
+
console.log(` Type: ${snapshot.project.type} | Stage: ${snapshot.project.lifecycleStage || "unknown"}`);
|
|
293
|
+
}
|
|
294
|
+
if (snapshot.project.methodVersion) {
|
|
295
|
+
console.log(` Method: v${snapshot.project.methodVersion} | Tier: ${snapshot.project.tier || "unknown"}`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// File inventory
|
|
299
|
+
const present = snapshot.methodologyFiles.present.length;
|
|
300
|
+
const missing = snapshot.methodologyFiles.missing.length;
|
|
301
|
+
console.log(`\n Methodology files: ${present} present, ${missing} missing`);
|
|
302
|
+
if (missing > 0) {
|
|
303
|
+
console.log(` Missing: ${snapshot.methodologyFiles.missing.join(", ")}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Threshold warnings
|
|
307
|
+
const nearThreshold = Object.entries(snapshot.fileStats)
|
|
308
|
+
.filter(([, s]) => s.nearThreshold);
|
|
309
|
+
if (nearThreshold.length > 0) {
|
|
310
|
+
console.log(` Scale warnings:`);
|
|
311
|
+
for (const [file, stats] of nearThreshold) {
|
|
312
|
+
console.log(` ! ${file}: ${stats.lines} lines (threshold: 300)`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Decisions
|
|
317
|
+
if (snapshot.decisions.count > 0) {
|
|
318
|
+
console.log(`\n Decisions: ${snapshot.decisions.count} total`);
|
|
319
|
+
if (snapshot.decisions.recent.length > 0) {
|
|
320
|
+
console.log(` Recent:`);
|
|
321
|
+
for (const d of snapshot.decisions.recent) {
|
|
322
|
+
const text = d.decision.length > 60 ? d.decision.slice(0, 57) + "..." : d.decision;
|
|
323
|
+
console.log(` ${d.date}: ${text}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Sessions
|
|
329
|
+
if (snapshot.sessions.count > 0) {
|
|
330
|
+
console.log(`\n Sessions: ${snapshot.sessions.count} logged (latest: ${snapshot.sessions.latestDate || "unknown"})`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Registry
|
|
334
|
+
if (snapshot.registry) {
|
|
335
|
+
console.log(`\n Registry: ${snapshot.registry.source} (${snapshot.registry.featureCount} features, ${snapshot.registry.workflowCount} workflows)`);
|
|
336
|
+
}
|
|
337
|
+
if (snapshot.features.length > 0) {
|
|
338
|
+
console.log(` Features referenced: ${snapshot.features.length} (${snapshot.features.slice(0, 8).join(", ")}${snapshot.features.length > 8 ? "..." : ""})`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Docs
|
|
342
|
+
if (snapshot.docsMap) {
|
|
343
|
+
const dm = snapshot.docsMap;
|
|
344
|
+
console.log(`\n Docs: ${dm.docsOnDisk}/${dm.inventoryCount} exist | ${dm.componentMappings} component mappings`);
|
|
345
|
+
if (dm.docsMissing > 0) {
|
|
346
|
+
console.log(` Missing docs: ${dm.docsMissing} (run \`wwa docs\` for details)`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ---------------------------------------------------------------------------
|
|
352
|
+
// Update project doc-tokens.yaml
|
|
353
|
+
// ---------------------------------------------------------------------------
|
|
354
|
+
|
|
355
|
+
async function updateProjectTokens(dir, snapshot) {
|
|
356
|
+
const tokensPath = join(dir, ".context", "doc-tokens.yaml");
|
|
357
|
+
if (!existsSync(tokensPath)) return null;
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
const yaml = (await import("js-yaml")).default;
|
|
361
|
+
const raw = readFileSync(tokensPath, "utf-8");
|
|
362
|
+
const parsed = yaml.load(raw) || {};
|
|
363
|
+
const tokens = parsed.tokens || {};
|
|
364
|
+
|
|
365
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
366
|
+
tokens.last_close = today;
|
|
367
|
+
|
|
368
|
+
// File counts — count methodology files (.md/.yaml/.yml) in project
|
|
369
|
+
let methodologyCount = 0;
|
|
370
|
+
let docsCount = 0;
|
|
371
|
+
let contextCount = 0;
|
|
372
|
+
const countFiles = (base) => {
|
|
373
|
+
if (!existsSync(base)) return;
|
|
374
|
+
for (const entry of readdirSync(base, { withFileTypes: true })) {
|
|
375
|
+
const p = join(base, entry.name);
|
|
376
|
+
if (entry.isDirectory()) {
|
|
377
|
+
// Track docs/ and .context/ separately
|
|
378
|
+
if (entry.name === "docs" && base === dir) {
|
|
379
|
+
docsCount += countDir(p);
|
|
380
|
+
} else if (entry.name === ".context" && base === dir) {
|
|
381
|
+
contextCount += countDir(p);
|
|
382
|
+
} else {
|
|
383
|
+
countFiles(p);
|
|
384
|
+
}
|
|
385
|
+
} else if (/\.(md|yaml|yml)$/i.test(entry.name)) {
|
|
386
|
+
methodologyCount++;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
const countDir = (d) => {
|
|
391
|
+
let c = 0;
|
|
392
|
+
if (!existsSync(d)) return c;
|
|
393
|
+
for (const entry of readdirSync(d, { withFileTypes: true })) {
|
|
394
|
+
if (entry.isDirectory()) c += countDir(join(d, entry.name));
|
|
395
|
+
else if (/\.(md|yaml|yml)$/i.test(entry.name)) c++;
|
|
396
|
+
}
|
|
397
|
+
return c;
|
|
398
|
+
};
|
|
399
|
+
countFiles(dir);
|
|
400
|
+
tokens.methodology_file_count = methodologyCount;
|
|
401
|
+
tokens.docs_file_count = docsCount;
|
|
402
|
+
tokens.context_file_count = contextCount;
|
|
403
|
+
|
|
404
|
+
// Session metrics
|
|
405
|
+
tokens.session_count = snapshot.sessions.count;
|
|
406
|
+
tokens.decision_count = snapshot.decisions.count;
|
|
407
|
+
tokens.total_features_referenced = snapshot.features.length;
|
|
408
|
+
|
|
409
|
+
// Scale indicators
|
|
410
|
+
let nearThreshold = 0;
|
|
411
|
+
let overThreshold = 0;
|
|
412
|
+
for (const f of snapshot.methodologyFiles.present) {
|
|
413
|
+
if (f.lines >= 300) overThreshold++;
|
|
414
|
+
else if (f.lines >= 250) nearThreshold++;
|
|
415
|
+
}
|
|
416
|
+
tokens.files_near_threshold = nearThreshold;
|
|
417
|
+
tokens.files_over_threshold = overThreshold;
|
|
418
|
+
|
|
419
|
+
// Docs coverage
|
|
420
|
+
if (snapshot.docsMap) {
|
|
421
|
+
tokens.docs_inventory_count = snapshot.docsMap.inventoryCount;
|
|
422
|
+
tokens.docs_on_disk = snapshot.docsMap.docsOnDisk;
|
|
423
|
+
tokens.docs_missing = snapshot.docsMap.docsMissing;
|
|
424
|
+
tokens.component_mappings = snapshot.docsMap.componentMappings;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Rebuild YAML output preserving structure
|
|
428
|
+
parsed.tokens = tokens;
|
|
429
|
+
const output = yaml.dump(parsed, { lineWidth: 120, noRefs: true, quotingType: '"' });
|
|
430
|
+
|
|
431
|
+
// Prepend header comment
|
|
432
|
+
const header = [
|
|
433
|
+
"# Project Token Registry",
|
|
434
|
+
"# Computed by wwa init. Updated by wwa close.",
|
|
435
|
+
"# Used by wwa casestudy for methodology reference data.",
|
|
436
|
+
"#",
|
|
437
|
+
`# Last updated: ${today}`,
|
|
438
|
+
"",
|
|
439
|
+
].join("\n");
|
|
440
|
+
|
|
441
|
+
safeWriteFile(tokensPath, header + output, "utf-8");
|
|
442
|
+
return tokens;
|
|
443
|
+
} catch (_) {
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
}
|