agent-method 1.5.13 → 1.5.15

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/lib/cli/close.js CHANGED
@@ -10,173 +10,196 @@ import { resolveTokensPath, resolveDocsMapPath, resolveIndexPath, resolveFeature
10
10
  const __filename = fileURLToPath(import.meta.url);
11
11
  const __dirname = dirname(__filename);
12
12
 
13
- export function register(program) {
14
- program
15
- .command("close [directory]")
16
- .description("Show session close checklist, cascade reminders, and project snapshot")
17
- .option("--json", "Output as JSON")
18
- .option("--internal-registry", "Include docs/internal/doc-registry.yaml health in management output")
19
- .action(async (directory, opts) => {
20
- directory = directory || ".";
21
- const d = resolve(directory);
13
+ export async function runClose(directory, options = {}) {
14
+ const d = resolve(directory || ".");
22
15
 
23
- const checklist = [];
24
- const warnings = [];
16
+ const checklist = [];
17
+ const warnings = [];
25
18
 
26
- // 1. Check SESSION-LOG.md exists
27
- const sessionLog = findSessionLog(directory);
28
- if (sessionLog) {
29
- checklist.push({ item: "SESSION-LOG.md exists", ok: true });
30
- } else {
31
- checklist.push({ item: "SESSION-LOG.md exists", ok: false });
32
- warnings.push("No SESSION-LOG.md found — create one to track session metrics");
33
- }
19
+ // 1. Check SESSION-LOG.md exists
20
+ const sessionLog = findSessionLog(d);
21
+ if (sessionLog) {
22
+ checklist.push({ item: "SESSION-LOG.md exists", ok: true });
23
+ } else {
24
+ checklist.push({ item: "SESSION-LOG.md exists", ok: false });
25
+ warnings.push("No SESSION-LOG.md found — create one to track session metrics");
26
+ }
34
27
 
35
- // 2. Check STATE.md exists and has current position
36
- const statePath = join(d, "STATE.md");
37
- if (existsSync(statePath)) {
38
- checklist.push({ item: "STATE.md exists", ok: true });
39
- } else {
40
- checklist.push({ item: "STATE.md exists", ok: false });
41
- warnings.push("No STATE.md found — decisions may be lost between sessions");
42
- }
28
+ // 2. Check STATE.md exists and has current position
29
+ const statePath = join(d, "STATE.md");
30
+ if (existsSync(statePath)) {
31
+ checklist.push({ item: "STATE.md exists", ok: true });
32
+ } else {
33
+ checklist.push({ item: "STATE.md exists", ok: false });
34
+ warnings.push("No STATE.md found — decisions may be lost between sessions");
35
+ }
43
36
 
44
- // 3. Check PLAN.md for incomplete steps
45
- const planPath = join(d, "PLAN.md");
46
- if (existsSync(planPath)) {
47
- const planContent = readFileSync(planPath, "utf-8");
48
- const todoSteps = (planContent.match(/\|\s*todo\s*\|/gi) || []).length;
49
- if (todoSteps > 0) {
50
- checklist.push({
51
- item: `PLAN.md has ${todoSteps} incomplete step(s)`,
52
- ok: false,
53
- });
54
- } else {
55
- checklist.push({ item: "PLAN.md — all steps complete", ok: true });
56
- }
57
- }
37
+ // 3. Check PLAN.md for incomplete steps
38
+ const planPath = join(d, "PLAN.md");
39
+ if (existsSync(planPath)) {
40
+ const planContent = readFileSync(planPath, "utf-8");
41
+ const todoSteps = (planContent.match(/\|\s*todo\s*\|/gi) || []).length;
42
+ if (todoSteps > 0) {
43
+ checklist.push({
44
+ item: `PLAN.md has ${todoSteps} incomplete step(s)`,
45
+ ok: false,
46
+ });
47
+ } else {
48
+ checklist.push({ item: "PLAN.md — all steps complete", ok: true });
49
+ }
50
+ }
58
51
 
59
- // 4. Check Management files
60
- const digestPath = join(d, "Management", "DIGEST.md");
61
- if (existsSync(digestPath)) {
62
- checklist.push({ item: "Management/DIGEST.md exists", ok: true });
63
- }
52
+ // 4. Check Management files
53
+ const digestPath = join(d, "Management", "DIGEST.md");
54
+ if (existsSync(digestPath)) {
55
+ checklist.push({ item: "Management/DIGEST.md exists", ok: true });
56
+ }
64
57
 
65
- // 5. Check SUMMARY.md
66
- const summaryPath = join(d, "SUMMARY.md");
67
- if (existsSync(summaryPath)) {
68
- checklist.push({ item: "SUMMARY.md exists for audit entry", ok: true });
69
- }
58
+ // 5. Check SUMMARY.md
59
+ const summaryPath = join(d, "SUMMARY.md");
60
+ if (existsSync(summaryPath)) {
61
+ checklist.push({ item: "SUMMARY.md exists for audit entry", ok: true });
62
+ }
70
63
 
71
- // Build cascade reminders from PROTOCOL.yaml or fallback to defaults.
72
- // If PROTOCOL.yaml has an empty close_check list, fall back to defaults so
73
- // the user always sees helpful reminders.
74
- let cascadeReminders;
75
- const protocolPath = join(d, ".context", "PROTOCOL.yaml");
76
- if (existsSync(protocolPath)) {
77
- try {
78
- const yaml = (await import("js-yaml")).default;
79
- let raw = readFileSync(protocolPath, "utf-8");
80
- const parsed = yaml.load(raw);
81
- cascadeReminders = parsed?.close_check || getDefaultReminders();
82
- // Update last_verified timestamp (comment-preserving)
83
- const today = new Date().toISOString().slice(0, 10);
84
- const lvPattern = /^(last_verified:) .+$/m;
85
- if (lvPattern.test(raw)) {
86
- raw = raw.replace(lvPattern, `$1 "${today}"`);
87
- } else {
88
- const vLine = /^(version: .+)$/m;
89
- raw = raw.replace(vLine, `$1\nlast_verified: "${today}"`);
90
- }
91
- safeWriteFile(protocolPath, raw, "utf-8");
92
- } catch (_) {
93
- cascadeReminders = getDefaultReminders();
94
- }
64
+ // Build cascade reminders from PROTOCOL.yaml or fallback to defaults.
65
+ // If PROTOCOL.yaml has an empty close_check list, fall back to defaults so
66
+ // the user always sees helpful reminders.
67
+ let cascadeReminders;
68
+ const protocolPath = join(d, ".context", "PROTOCOL.yaml");
69
+ if (existsSync(protocolPath)) {
70
+ try {
71
+ const yaml = (await import("js-yaml")).default;
72
+ let raw = readFileSync(protocolPath, "utf-8");
73
+ const parsed = yaml.load(raw);
74
+ cascadeReminders = parsed?.close_check || getDefaultReminders();
75
+ // Update last_verified timestamp (comment-preserving)
76
+ const today = new Date().toISOString().slice(0, 10);
77
+ const lvPattern = /^(last_verified:) .+$/m;
78
+ if (lvPattern.test(raw)) {
79
+ raw = raw.replace(lvPattern, `$1 "${today}"`);
95
80
  } else {
96
- cascadeReminders = getDefaultReminders();
81
+ const vLine = /^(version: .+)$/m;
82
+ raw = raw.replace(vLine, `$1\nlast_verified: "${today}"`);
97
83
  }
84
+ safeWriteFile(protocolPath, raw, "utf-8");
85
+ } catch (_) {
86
+ cascadeReminders = getDefaultReminders();
87
+ }
88
+ } else {
89
+ cascadeReminders = getDefaultReminders();
90
+ }
98
91
 
99
- // If we still have no reminders (empty array), use defaults.
100
- if (!cascadeReminders || cascadeReminders.length === 0) {
101
- cascadeReminders = getDefaultReminders();
102
- }
92
+ // If we still have no reminders (empty array), use defaults.
93
+ if (!cascadeReminders || cascadeReminders.length === 0) {
94
+ cascadeReminders = getDefaultReminders();
95
+ }
103
96
 
104
- // Compute project snapshot — methodology reference data
105
- const snapshot = await computeProjectSnapshot(d);
97
+ // Compute project snapshot — methodology reference data
98
+ const snapshot = await computeProjectSnapshot(d);
106
99
 
107
- // Update project doc-tokens.yaml regardless of output mode
108
- const tokenUpdateResult = await updateProjectTokens(d, snapshot);
100
+ // Update project doc-tokens.yaml regardless of output mode
101
+ const tokenUpdateResult = await updateProjectTokens(d, snapshot);
109
102
 
110
- // Update INDEX.yaml metrics
111
- const indexUpdateResult = await updateIndexMetrics(d, snapshot);
103
+ // Update INDEX.yaml metrics
104
+ const indexUpdateResult = await updateIndexMetrics(d, snapshot);
112
105
 
113
- // Load internal registry if requested
114
- let internalRegistry = null;
115
- if (opts.internalRegistry) {
116
- try {
117
- const { loadInternalRegistry } = await import("./doc-review.js");
118
- internalRegistry = await loadInternalRegistry(d);
119
- if (!internalRegistry) {
120
- console.log(" Note: No docs/internal/doc-registry.yaml found run `wwa doc-review` to generate it.");
121
- }
122
- } catch {
123
- // doc-review module not available — skip silently
124
- }
125
- }
106
+ // Load internal registry if requested
107
+ let internalRegistry = null;
108
+ if (options.internalRegistry) {
109
+ try {
110
+ const { loadInternalRegistry } = await import("./doc-review.js");
111
+ internalRegistry = await loadInternalRegistry(d);
112
+ } catch {
113
+ // doc-review module not available skip silently
114
+ }
115
+ }
126
116
 
127
- // Always write management files (close-snapshot.yaml + STATUS.md) at close.
128
- const writeResults = await writeManagementFiles(
129
- d,
130
- snapshot,
131
- checklist,
132
- cascadeReminders,
133
- internalRegistry
134
- );
117
+ // Always write management files (close-snapshot.yaml + STATUS.md) at close.
118
+ const writeResults = await writeManagementFiles(
119
+ d,
120
+ snapshot,
121
+ checklist,
122
+ cascadeReminders,
123
+ internalRegistry
124
+ );
135
125
 
136
- if (opts.json) {
137
- console.log(
138
- JSON.stringify({ checklist, warnings, cascadeReminders, snapshot, tokensUpdated: !!tokenUpdateResult, indexUpdated: !!indexUpdateResult, writeResults }, null, 2)
139
- );
140
- return;
141
- }
126
+ return {
127
+ directory: d,
128
+ checklist,
129
+ warnings,
130
+ cascadeReminders,
131
+ snapshot,
132
+ tokensUpdated: !!tokenUpdateResult,
133
+ indexUpdated: !!indexUpdateResult,
134
+ writeResults,
135
+ internalRegistryLoaded: !!internalRegistry,
136
+ };
137
+ }
138
+
139
+ export function register(program) {
140
+ program
141
+ .command("close [directory]")
142
+ .description("Show session close checklist, cascade reminders, and project snapshot")
143
+ .option("--json", "Output as JSON")
144
+ .option(
145
+ "--internal-registry",
146
+ "Include docs/internal/doc-registry.yaml health in management output"
147
+ )
148
+ .action(async (directory, opts) => {
149
+ const d = directory || ".";
150
+ try {
151
+ const result = await runClose(d, {
152
+ internalRegistry: opts.internalRegistry,
153
+ });
142
154
 
143
- console.log("\n Session Close Checklist\n");
155
+ if (opts.json) {
156
+ console.log(JSON.stringify(result, null, 2));
157
+ return;
158
+ }
144
159
 
145
- for (const item of checklist) {
146
- const icon = item.ok ? "[x]" : "[ ]";
147
- console.log(` ${icon} ${item.item}`);
148
- }
160
+ console.log("\n Session Close Checklist\n");
149
161
 
150
- if (warnings.length > 0) {
151
- console.log("\n Warnings:");
152
- for (const w of warnings) {
153
- console.log(` ! ${w}`);
162
+ for (const item of result.checklist) {
163
+ const icon = item.ok ? "[x]" : "[ ]";
164
+ console.log(` ${icon} ${item.item}`);
154
165
  }
155
- }
156
166
 
157
- console.log("\n Cascade reminders:");
158
- for (const r of cascadeReminders) {
159
- console.log(` - ${r}`);
160
- }
167
+ if (result.warnings.length > 0) {
168
+ console.log("\n Warnings:");
169
+ for (const w of result.warnings) {
170
+ console.log(` ! ${w}`);
171
+ }
172
+ }
161
173
 
162
- // Print project snapshot
163
- printSnapshot(snapshot);
174
+ console.log("\n Cascade reminders:");
175
+ for (const r of result.cascadeReminders) {
176
+ console.log(` - ${r}`);
177
+ }
164
178
 
165
- if (tokenUpdateResult) {
166
- console.log("\n Updated doc-tokens.yaml with current project metrics.");
167
- }
179
+ // Print project snapshot
180
+ printSnapshot(result.snapshot);
168
181
 
169
- if (indexUpdateResult) {
170
- console.log(" Updated INDEX.yaml with current project metrics.");
171
- }
182
+ if (result.tokensUpdated) {
183
+ console.log("\n Updated doc-tokens.yaml with current project metrics.");
184
+ }
172
185
 
173
- if (writeResults) {
174
- console.log("\n Management files written:");
175
- if (writeResults.snapshotYaml) console.log(` - ${writeResults.snapshotYaml}`);
176
- if (writeResults.statusMd) console.log(` - ${writeResults.statusMd}`);
177
- }
186
+ if (result.indexUpdated) {
187
+ console.log(" Updated INDEX.yaml with current project metrics.");
188
+ }
189
+
190
+ if (result.writeResults) {
191
+ console.log("\n Management files written:");
192
+ if (result.writeResults.snapshotYaml)
193
+ console.log(` - ${result.writeResults.snapshotYaml}`);
194
+ if (result.writeResults.statusMd)
195
+ console.log(` - ${result.writeResults.statusMd}`);
196
+ }
178
197
 
179
- console.log("");
198
+ console.log("");
199
+ } catch (e) {
200
+ console.error(e.message || "wwa close failed.");
201
+ process.exit(1);
202
+ }
180
203
  });
181
204
  }
182
205
 
@@ -22,6 +22,19 @@ export const pkg = _pkg;
22
22
  /** Package root directory (two levels up from lib/cli/). */
23
23
  export const packageRoot = resolve(__dirname, "..", "..");
24
24
 
25
+ // ---------------------------------------------------------------------------
26
+ // Archive and naming invariant (all wwa commands)
27
+ // ---------------------------------------------------------------------------
28
+ //
29
+ // Canonical names persist in the working directory: CLAUDE.md, .cursorrules,
30
+ // AGENT.md, registry/doc-tokens.yaml, etc. Wwa never writes versioned names
31
+ // (e.g. CLAUDE.v2.md or doc-tokens.v2.yaml) into the project root. When we
32
+ // need to keep a previous version, we put it under the archive subdirectory
33
+ // only (e.g. .wwa-archive/entry-points/CLAUDE.v2.md).
34
+
35
+ /** Archive root for versioned/archived copies. Versioned filenames exist only under this tree. */
36
+ export const WWA_ARCHIVE_DIR = ".wwa-archive";
37
+
25
38
  // ---------------------------------------------------------------------------
26
39
  // Project type aliases
27
40
  // ---------------------------------------------------------------------------
package/lib/cli/record.js CHANGED
@@ -11,6 +11,71 @@ import {
11
11
  parseLastRunContent,
12
12
  } from "./report-helpers.js";
13
13
 
14
+ /**
15
+ * Core implementation for `wwa record` and MCP write-safe tools.
16
+ */
17
+ export async function runRecord(directory, options = {}) {
18
+ const dir = resolve(directory || ".");
19
+ const dryRun = !!options.dryRun;
20
+
21
+ const content = readLastRunSummary(dir);
22
+ if (!content) {
23
+ const msg =
24
+ "No recent CLI output to record. Run a wwa command (e.g. wwa route '…', wwa scan) first.";
25
+ const error = new Error(msg);
26
+ error.code = "NO_LAST_RUN";
27
+ throw error;
28
+ }
29
+
30
+ const parsed = parseLastRunContent(content);
31
+ if (!parsed) {
32
+ const msg = "Last run file was invalid; nothing to record.";
33
+ const error = new Error(msg);
34
+ error.code = "INVALID_LAST_RUN";
35
+ throw error;
36
+ }
37
+
38
+ const date = parsed.timestamp.slice(0, 10);
39
+ const block = [
40
+ "",
41
+ `### CLI run — ${date} — ${parsed.command}`,
42
+ "",
43
+ `Command: wwa ${parsed.command}`,
44
+ `Input: ${parsed.inputRef}`,
45
+ `Summary: ${parsed.summary}`,
46
+ "",
47
+ ].join("\n");
48
+
49
+ const sessionLog = findSessionLog(dir);
50
+ if (!sessionLog) {
51
+ const msg = "No SESSION-LOG.md found. Create it or run from project root.";
52
+ const error = new Error(msg);
53
+ error.code = "NO_SESSION_LOG";
54
+ throw error;
55
+ }
56
+
57
+ if (dryRun) {
58
+ return {
59
+ directory: dir,
60
+ target: sessionLog,
61
+ block: block.trim(),
62
+ dryRun: true,
63
+ recorded: false,
64
+ };
65
+ }
66
+
67
+ const existing = readFileSync(sessionLog, "utf-8");
68
+ safeWriteFile(sessionLog, existing + block, "utf-8");
69
+ clearLastRunSummary(dir);
70
+
71
+ return {
72
+ directory: dir,
73
+ target: sessionLog,
74
+ dryRun: false,
75
+ recorded: true,
76
+ };
77
+ }
78
+
14
79
  export function register(program) {
15
80
  program
16
81
  .command("record [directory]")
@@ -19,80 +84,37 @@ export function register(program) {
19
84
  .option("--dry-run", "Show what would be appended without writing")
20
85
  .option("--json", "Output as JSON")
21
86
  .action(async (directory, cmd) => {
22
- const opts = typeof cmd?.opts === "function" ? cmd.opts() : (cmd || {});
23
- const dir = resolve(opts.directory || directory || ".");
24
- const content = readLastRunSummary(dir);
87
+ const opts = typeof cmd?.opts === "function" ? cmd.opts() : cmd || {};
88
+ // Prefer explicit directory argument over option default so tests and callers
89
+ // can target a specific project root without being affected by cwd state.
90
+ const dir = resolve(directory || opts.directory || ".");
25
91
 
26
- if (!content) {
27
- const msg =
28
- "No recent CLI output to record. Run a wwa command (e.g. wwa route '…', wwa scan) first.";
29
- if (opts.json) {
30
- console.log(JSON.stringify({ recorded: false, error: msg }, null, 2));
31
- } else {
32
- console.log(`\n ${msg}\n`);
33
- }
34
- return;
35
- }
92
+ try {
93
+ const result = await runRecord(dir, { dryRun: opts.dryRun });
36
94
 
37
- const parsed = parseLastRunContent(content);
38
- if (!parsed) {
39
- const msg = "Last run file was invalid; nothing to record.";
40
95
  if (opts.json) {
41
- console.log(JSON.stringify({ recorded: false, error: msg }, null, 2));
42
- } else {
43
- console.log(`\n ${msg}\n`);
96
+ console.log(JSON.stringify(result, null, 2));
97
+ return;
44
98
  }
45
- return;
46
- }
47
-
48
- const date = parsed.timestamp.slice(0, 10);
49
- const block = [
50
- "",
51
- `### CLI run — ${date} — ${parsed.command}`,
52
- "",
53
- `Command: wwa ${parsed.command}`,
54
- `Input: ${parsed.inputRef}`,
55
- `Summary: ${parsed.summary}`,
56
- "",
57
- ].join("\n");
58
99
 
59
- const sessionLog = findSessionLog(dir);
60
- if (!sessionLog) {
61
- const msg = "No SESSION-LOG.md found. Create it or run from project root.";
62
- if (opts.json) {
63
- console.log(JSON.stringify({ recorded: false, error: msg }, null, 2));
64
- } else {
65
- console.log(`\n ${msg}\n`);
100
+ if (result.dryRun) {
101
+ console.log("\n Dry run — would append to", result.target);
102
+ console.log(result.block);
103
+ } else if (result.recorded) {
104
+ console.log("\n Recorded last CLI run to SESSION-LOG.md.\n");
66
105
  }
67
- return;
68
- }
69
-
70
- if (opts.dryRun) {
106
+ } catch (e) {
71
107
  if (opts.json) {
72
108
  console.log(
73
109
  JSON.stringify(
74
- { dryRun: true, target: sessionLog, block: block.trim() },
110
+ { recorded: false, error: e.message, code: e.code || null },
75
111
  null,
76
112
  2
77
113
  )
78
114
  );
79
115
  } else {
80
- console.log("\n Dry run — would append to", sessionLog);
81
- console.log(block);
116
+ console.log(`\n ${e.message}\n`);
82
117
  }
83
- return;
84
- }
85
-
86
- const existing = readFileSync(sessionLog, "utf-8");
87
- safeWriteFile(sessionLog, existing + block, "utf-8");
88
- clearLastRunSummary(dir);
89
-
90
- if (opts.json) {
91
- console.log(
92
- JSON.stringify({ recorded: true, target: sessionLog }, null, 2)
93
- );
94
- } else {
95
- console.log("\n Recorded last CLI run to SESSION-LOG.md.\n");
96
118
  }
97
119
  });
98
120
  }