agent-method 1.5.12 → 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/bin/wwa.js +1 -1
- package/docs/internal/doc-tokens.yaml +1 -1
- package/lib/cli/add.js +170 -96
- package/lib/cli/casestudy.js +152 -92
- package/lib/cli/close.js +164 -141
- package/lib/cli/helpers.js +13 -0
- package/lib/cli/record.js +81 -59
- package/lib/cli/upgrade.js +144 -2
- package/lib/init.js +1 -2
- package/lib/mcp-server.js +842 -1
- package/package.json +1 -1
package/lib/cli/upgrade.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* wwa upgrade — brownfield-safe methodology update.
|
|
3
|
+
*
|
|
4
|
+
* Invariant (all wwa commands): canonical names persist in the working directory;
|
|
5
|
+
* versioned/archived copies live only under .wwa-archive/ (see helpers.WWA_ARCHIVE_DIR).
|
|
6
|
+
*/
|
|
2
7
|
|
|
3
8
|
import {
|
|
4
9
|
readFileSync, existsSync, mkdirSync, copyFileSync, unlinkSync,
|
|
@@ -6,7 +11,7 @@ import {
|
|
|
6
11
|
import { resolve, join, dirname } from "node:path";
|
|
7
12
|
import {
|
|
8
13
|
findEntryPoint, readMethodVersion, basename_of, pkg, packageRoot,
|
|
9
|
-
safeWriteFile, safeCopyFile,
|
|
14
|
+
safeWriteFile, safeCopyFile, WWA_ARCHIVE_DIR,
|
|
10
15
|
} from "./helpers.js";
|
|
11
16
|
|
|
12
17
|
export function register(program) {
|
|
@@ -206,6 +211,67 @@ export function register(program) {
|
|
|
206
211
|
}
|
|
207
212
|
}
|
|
208
213
|
|
|
214
|
+
// 6. Non-destructive template/schema upgrades (archived originals + live latest)
|
|
215
|
+
//
|
|
216
|
+
// When templates or schemas change, we never delete user content. Instead we:
|
|
217
|
+
// - Archive the existing file into a subdirectory (e.g. .wwa-archive/entry-points/CLAUDE.md)
|
|
218
|
+
// - Replace the live file with the latest template/schema
|
|
219
|
+
// - Add a backlog entry pointing to the archived copy so migration can be reviewed.
|
|
220
|
+
|
|
221
|
+
// 6a. Entry point templates (CLAUDE.md / .cursorrules / AGENT.md)
|
|
222
|
+
if (ep) {
|
|
223
|
+
const entryName = basename_of(ep); // e.g. CLAUDE.md
|
|
224
|
+
const templatePath = join(srcDir, entryName);
|
|
225
|
+
if (existsSync(templatePath)) {
|
|
226
|
+
const change = classifyMarkdownChange(templatePath, ep);
|
|
227
|
+
if (change === "present_diverged_user") {
|
|
228
|
+
const archiveDir = join(d, WWA_ARCHIVE_DIR, "entry-points");
|
|
229
|
+
const baseArchivePath = join(archiveDir, entryName);
|
|
230
|
+
const archivePath = nextVersionedPathInsideArchive(archiveDir, entryName);
|
|
231
|
+
const relArchive = archivePath.replace(d, "").replace(/^[\\/]/, "");
|
|
232
|
+
actions.push(`Archive existing entry point to ${relArchive} and replace with latest template`);
|
|
233
|
+
if (!opts.dryRun) {
|
|
234
|
+
mkdirSync(dirname(archivePath), { recursive: true });
|
|
235
|
+
safeCopyFile(ep, archivePath);
|
|
236
|
+
safeCopyFile(templatePath, ep);
|
|
237
|
+
}
|
|
238
|
+
ensureBacklogEntry(
|
|
239
|
+
d,
|
|
240
|
+
relArchive,
|
|
241
|
+
`- [ ] **Review upgraded entry point** — Existing entry point archived at ${relArchive}. Compare with current ${entryName} and reconcile any custom conventions.`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 6b. Registry/doc-tokens schema (YAML)
|
|
248
|
+
const projectTokensPath = join(d, "registry", "doc-tokens.yaml");
|
|
249
|
+
const templateTokensPath = join(
|
|
250
|
+
packageRoot,
|
|
251
|
+
"docs",
|
|
252
|
+
"internal",
|
|
253
|
+
"doc-tokens.yaml"
|
|
254
|
+
);
|
|
255
|
+
if (existsSync(projectTokensPath) && existsSync(templateTokensPath)) {
|
|
256
|
+
const change = classifyYamlChange(templateTokensPath, projectTokensPath);
|
|
257
|
+
if (change === "schema_changed" || change === "present_diverged_user") {
|
|
258
|
+
const archiveDir = join(d, WWA_ARCHIVE_DIR, "registry");
|
|
259
|
+
const archivePath = nextVersionedPathInsideArchive(archiveDir, "doc-tokens.yaml");
|
|
260
|
+
const relArchive = archivePath.replace(d, "").replace(/^[\\/]/, "");
|
|
261
|
+
actions.push(`Archive existing registry/doc-tokens.yaml to ${relArchive} and replace with latest schema`);
|
|
262
|
+
if (!opts.dryRun) {
|
|
263
|
+
mkdirSync(dirname(archivePath), { recursive: true });
|
|
264
|
+
safeCopyFile(projectTokensPath, archivePath);
|
|
265
|
+
safeCopyFile(templateTokensPath, projectTokensPath);
|
|
266
|
+
}
|
|
267
|
+
ensureBacklogEntry(
|
|
268
|
+
d,
|
|
269
|
+
relArchive,
|
|
270
|
+
`- [ ] **Review doc-tokens schema migration** — Previous schema archived at ${relArchive}. Confirm that tooling and docs are compatible with the updated registry/doc-tokens.yaml.`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
209
275
|
// Report
|
|
210
276
|
if (actions.length === 0) {
|
|
211
277
|
console.log(`Project at ${directory} is up to date (v${pkg.version}).`);
|
|
@@ -220,3 +286,79 @@ export function register(program) {
|
|
|
220
286
|
}
|
|
221
287
|
});
|
|
222
288
|
}
|
|
289
|
+
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
// Helper functions — change classification, archive paths, backlog entries
|
|
292
|
+
// ---------------------------------------------------------------------------
|
|
293
|
+
//
|
|
294
|
+
// Versioned filenames are only ever created inside the archive subdirectory,
|
|
295
|
+
// never in the working directory, so canonical names (CLAUDE.md, doc-tokens.yaml)
|
|
296
|
+
// always persist in place.
|
|
297
|
+
|
|
298
|
+
function classifyMarkdownChange(templatePath, projectPath) {
|
|
299
|
+
if (!existsSync(projectPath) || !existsSync(templatePath)) return "missing";
|
|
300
|
+
const tmpl = readFileSync(templatePath, "utf-8");
|
|
301
|
+
const proj = readFileSync(projectPath, "utf-8");
|
|
302
|
+
if (tmpl === proj) return "present_identical";
|
|
303
|
+
return "present_diverged_user";
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function classifyYamlChange(templatePath, projectPath) {
|
|
307
|
+
if (!existsSync(projectPath) || !existsSync(templatePath)) return "missing";
|
|
308
|
+
const tmpl = readFileSync(templatePath, "utf-8");
|
|
309
|
+
const proj = readFileSync(projectPath, "utf-8");
|
|
310
|
+
if (tmpl === proj) return "present_identical";
|
|
311
|
+
// Treat any content difference as a schema-level change for now; this is
|
|
312
|
+
// conservative but guarantees we keep the original file intact.
|
|
313
|
+
return "schema_changed";
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Return a path for an archived copy, with a version suffix only inside the archive dir.
|
|
318
|
+
* Ensures we never write *.v2.* in the working directory — only under archiveDir.
|
|
319
|
+
*/
|
|
320
|
+
function nextVersionedPathInsideArchive(archiveDir, filename) {
|
|
321
|
+
const match = filename.match(/^(.*)(\.[^./\\]+)$/);
|
|
322
|
+
const stem = match ? match[1] : filename;
|
|
323
|
+
const ext = match ? match[2] : "";
|
|
324
|
+
let counter = 2;
|
|
325
|
+
for (;;) {
|
|
326
|
+
const candidate = join(archiveDir, `${stem}.v${counter}${ext}`);
|
|
327
|
+
if (!existsSync(candidate)) return candidate;
|
|
328
|
+
counter += 1;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function ensureBacklogEntry(projectDir, markerPath, line) {
|
|
333
|
+
const backlogCandidates = [
|
|
334
|
+
join(projectDir, "todos", "backlog.md"),
|
|
335
|
+
join(projectDir, "backlog.md"),
|
|
336
|
+
];
|
|
337
|
+
|
|
338
|
+
let targetPath = null;
|
|
339
|
+
let content = "";
|
|
340
|
+
|
|
341
|
+
for (const p of backlogCandidates) {
|
|
342
|
+
if (existsSync(p)) {
|
|
343
|
+
const existing = readFileSync(p, "utf-8");
|
|
344
|
+
if (existing.includes(markerPath)) {
|
|
345
|
+
return; // entry already present
|
|
346
|
+
}
|
|
347
|
+
targetPath = p;
|
|
348
|
+
content = existing;
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (!targetPath) {
|
|
354
|
+
targetPath = backlogCandidates[0];
|
|
355
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
356
|
+
content = "# Backlog\n\n";
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (content && !content.endsWith("\n")) {
|
|
360
|
+
content += "\n";
|
|
361
|
+
}
|
|
362
|
+
content += `${line}\n`;
|
|
363
|
+
safeWriteFile(targetPath, content, "utf-8");
|
|
364
|
+
}
|
package/lib/init.js
CHANGED
|
@@ -57,8 +57,7 @@ export async function initProject(projectType, directory, opts = {}) {
|
|
|
57
57
|
|
|
58
58
|
const templateDir = join(methodRoot, "templates", tier);
|
|
59
59
|
if (!existsSync(templateDir)) {
|
|
60
|
-
|
|
61
|
-
process.exit(1);
|
|
60
|
+
throw new Error(`Template directory not found: ${templateDir}`);
|
|
62
61
|
}
|
|
63
62
|
|
|
64
63
|
console.log(`\nSetting up ${FRIENDLY_NAMES[projectType] || projectType} project`);
|