agent-method 1.5.0

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.
Files changed (52) hide show
  1. package/README.md +256 -0
  2. package/bin/agent-method.js +58 -0
  3. package/docs/internal/feature-registry.yaml +1532 -0
  4. package/lib/cli/check.js +71 -0
  5. package/lib/cli/helpers.js +151 -0
  6. package/lib/cli/init.js +60 -0
  7. package/lib/cli/pipeline.js +163 -0
  8. package/lib/cli/refine.js +202 -0
  9. package/lib/cli/route.js +62 -0
  10. package/lib/cli/scan.js +28 -0
  11. package/lib/cli/status.js +61 -0
  12. package/lib/cli/upgrade.js +146 -0
  13. package/lib/init.js +240 -0
  14. package/lib/pipeline.js +887 -0
  15. package/lib/registry.js +108 -0
  16. package/package.json +39 -0
  17. package/templates/README.md +293 -0
  18. package/templates/entry-points/.cursorrules +109 -0
  19. package/templates/entry-points/AGENT.md +109 -0
  20. package/templates/entry-points/CLAUDE.md +109 -0
  21. package/templates/extensions/MANIFEST.md +110 -0
  22. package/templates/extensions/analytical-system.md +96 -0
  23. package/templates/extensions/code-project.md +77 -0
  24. package/templates/extensions/data-exploration.md +117 -0
  25. package/templates/full/.context/BASE.md +68 -0
  26. package/templates/full/.context/COMPOSITION.md +47 -0
  27. package/templates/full/.context/METHODOLOGY.md +84 -0
  28. package/templates/full/.context/REGISTRY.md +75 -0
  29. package/templates/full/.cursorrules +128 -0
  30. package/templates/full/AGENT.md +128 -0
  31. package/templates/full/CLAUDE.md +128 -0
  32. package/templates/full/PLAN.md +67 -0
  33. package/templates/full/PROJECT-PROFILE.md +61 -0
  34. package/templates/full/PROJECT.md +46 -0
  35. package/templates/full/REQUIREMENTS.md +30 -0
  36. package/templates/full/ROADMAP.md +39 -0
  37. package/templates/full/SESSION-LOG.md +41 -0
  38. package/templates/full/STATE.md +42 -0
  39. package/templates/full/SUMMARY.md +24 -0
  40. package/templates/full/docs/index.md +46 -0
  41. package/templates/full/todos/backlog.md +19 -0
  42. package/templates/starter/.context/BASE.md +66 -0
  43. package/templates/starter/.context/METHODOLOGY.md +70 -0
  44. package/templates/starter/.cursorrules +113 -0
  45. package/templates/starter/AGENT.md +113 -0
  46. package/templates/starter/CLAUDE.md +113 -0
  47. package/templates/starter/PLAN.md +67 -0
  48. package/templates/starter/PROJECT-PROFILE.md +44 -0
  49. package/templates/starter/PROJECT.md +46 -0
  50. package/templates/starter/ROADMAP.md +39 -0
  51. package/templates/starter/SESSION-LOG.md +41 -0
  52. package/templates/starter/STATE.md +42 -0
@@ -0,0 +1,62 @@
1
+ /** agent-method route — show how a query routes through the pipeline. */
2
+
3
+ import {
4
+ resolveProjectType,
5
+ getPipeline,
6
+ loadRegistryData,
7
+ } from "./helpers.js";
8
+
9
+ export function register(program) {
10
+ program
11
+ .command("route <query>")
12
+ .description("Show how a query routes through the pipeline")
13
+ .option(
14
+ "-p, --project-type <type>",
15
+ "Project type: code, context, data, mix, general (auto-detected if omitted)"
16
+ )
17
+ .option("-s, --stage <stage>", "Workflow stage", "scope")
18
+ .option("--first-session", "Force WF-04 bootstrap")
19
+ .option("--registry <path>", "Path to feature-registry.yaml")
20
+ .option("--json", "Output as JSON")
21
+ .action(async (query, opts) => {
22
+ const { route, detectProjectType } = await getPipeline();
23
+ const reg = await loadRegistryData(opts.registry);
24
+
25
+ let projectType;
26
+ if (opts.projectType) {
27
+ projectType = resolveProjectType(opts.projectType);
28
+ } else {
29
+ const detected = detectProjectType(".");
30
+ projectType = detected.project_type || "general";
31
+ }
32
+
33
+ const result = route(
34
+ query,
35
+ projectType,
36
+ opts.stage,
37
+ reg,
38
+ opts.firstSession || false
39
+ );
40
+ console.log(`Routing: "${query}" (type: ${projectType})`);
41
+
42
+ if (opts.json) {
43
+ console.log(JSON.stringify(result, null, 2));
44
+ } else {
45
+ for (const section of [
46
+ "S1_classify",
47
+ "S2_select",
48
+ "S3_resolve",
49
+ "S4_compute",
50
+ ]) {
51
+ console.log(`\n ${section}:`);
52
+ for (const [k, v] of Object.entries(result[section])) {
53
+ if (Array.isArray(v)) {
54
+ console.log(` ${k}: [${v.join(", ")}]`);
55
+ } else {
56
+ console.log(` ${k}: ${v}`);
57
+ }
58
+ }
59
+ }
60
+ }
61
+ });
62
+ }
@@ -0,0 +1,28 @@
1
+ /** agent-method scan — detect project type from directory contents. */
2
+
3
+ import { getPipeline, outputData } from "./helpers.js";
4
+
5
+ export function register(program) {
6
+ program
7
+ .command("scan [directory]")
8
+ .description("Detect project type from directory contents")
9
+ .option("--registry <path>", "Path to feature-registry.yaml")
10
+ .option("--json", "Output as JSON")
11
+ .action(async (directory, opts) => {
12
+ directory = directory || ".";
13
+ const { detectProjectType } = await getPipeline();
14
+ const result = detectProjectType(directory);
15
+
16
+ const ptype = result.project_type || "general";
17
+ const friendlyMap = { analytical: "context", mixed: "mix" };
18
+ if (ptype in friendlyMap) {
19
+ result.friendly_name = friendlyMap[ptype];
20
+ result.init_command = `agent-method init ${friendlyMap[ptype]}`;
21
+ } else {
22
+ result.init_command = `agent-method init ${ptype}`;
23
+ }
24
+
25
+ console.log(`Scanning: ${directory}`);
26
+ outputData(result, opts.json);
27
+ });
28
+ }
@@ -0,0 +1,61 @@
1
+ /** agent-method status — check if methodology version is current. */
2
+
3
+ import { findEntryPoint, readMethodVersion, basename_of, pkg } from "./helpers.js";
4
+
5
+ export function register(program) {
6
+ program
7
+ .command("status [directory]")
8
+ .description("Check if your methodology version is current")
9
+ .option("--json", "Output as JSON")
10
+ .action(async (directory, opts) => {
11
+ directory = directory || ".";
12
+ const ep = findEntryPoint(directory);
13
+ if (!ep) {
14
+ console.error(
15
+ `No entry point found in ${directory} ` +
16
+ "(looked for CLAUDE.md, .cursorrules, AGENT.md)"
17
+ );
18
+ process.exit(1);
19
+ }
20
+
21
+ const epVersion = readMethodVersion(ep);
22
+ const installed = pkg.version;
23
+
24
+ let verStatus, message;
25
+ if (!epVersion) {
26
+ verStatus = "no_version";
27
+ message =
28
+ `Entry point ${basename_of(ep)} has no method_version setting. ` +
29
+ `Add 'method_version: ${installed.split(".").slice(0, 2).join(".")}' ` +
30
+ `to enable version tracking.`;
31
+ } else if (epVersion === installed.split(".").slice(0, 2).join(".")) {
32
+ verStatus = "current";
33
+ message =
34
+ `Entry point ${basename_of(ep)} is current ` +
35
+ `(method_version: ${epVersion}, installed: ${installed})`;
36
+ } else {
37
+ verStatus = "outdated";
38
+ message =
39
+ `Entry point ${basename_of(ep)} is outdated ` +
40
+ `(method_version: ${epVersion}, installed: ${installed}). ` +
41
+ `Run \`agent-method upgrade ${directory}\` to update.`;
42
+ }
43
+
44
+ if (opts.json) {
45
+ console.log(
46
+ JSON.stringify(
47
+ {
48
+ entry_point: ep,
49
+ entry_point_version: epVersion,
50
+ installed_version: installed,
51
+ status: verStatus,
52
+ },
53
+ null,
54
+ 2
55
+ )
56
+ );
57
+ } else {
58
+ console.log(message);
59
+ }
60
+ });
61
+ }
@@ -0,0 +1,146 @@
1
+ /** agent-method upgrade — brownfield-safe methodology update. */
2
+
3
+ import {
4
+ readFileSync, existsSync, copyFileSync, mkdirSync, writeFileSync,
5
+ } from "node:fs";
6
+ import { resolve, join, dirname } from "node:path";
7
+ import {
8
+ findEntryPoint, readMethodVersion, basename_of, pkg, packageRoot,
9
+ } from "./helpers.js";
10
+
11
+ export function register(program) {
12
+ program
13
+ .command("upgrade [directory]")
14
+ .description("Update your project to the latest methodology version")
15
+ .option("--dry-run", "Show what would change without modifying files")
16
+ .action(async (directory, opts) => {
17
+ directory = directory || ".";
18
+ const d = resolve(directory);
19
+ const ep = findEntryPoint(directory);
20
+ const actions = [];
21
+
22
+ const templateDirs = {
23
+ starter: join(packageRoot, "templates", "starter"),
24
+ full: join(packageRoot, "templates", "full"),
25
+ };
26
+
27
+ // Determine which template tier this project uses
28
+ let tier = "starter";
29
+ if (existsSync(join(d, ".context", "REGISTRY.md"))) {
30
+ tier = "full";
31
+ } else if (
32
+ existsSync(join(d, "REQUIREMENTS.md")) &&
33
+ existsSync(join(d, "SUMMARY.md"))
34
+ ) {
35
+ tier = "full";
36
+ }
37
+ const srcDir = templateDirs[tier] || templateDirs.starter;
38
+
39
+ if (!existsSync(srcDir)) {
40
+ console.error(
41
+ `Template directory not found: ${srcDir}. ` +
42
+ "Run from the agent-method repo or install from source."
43
+ );
44
+ process.exit(1);
45
+ }
46
+
47
+ // 1. Update method_version in entry point
48
+ if (ep) {
49
+ let content = readFileSync(ep, "utf-8");
50
+ const currentVer = readMethodVersion(ep);
51
+ const newVer = pkg.version.split(".").slice(0, 2).join(".");
52
+ if (currentVer && currentVer !== newVer) {
53
+ actions.push(
54
+ `Update ${basename_of(ep)}: method_version ${currentVer} -> ${newVer}`
55
+ );
56
+ if (!opts.dryRun) {
57
+ content = content.replace(/(method_version:\s*)\S+/, `$1${newVer}`);
58
+ writeFileSync(ep, content, "utf-8");
59
+ }
60
+ } else if (!currentVer) {
61
+ actions.push(`Add method_version: ${newVer} to ${basename_of(ep)}`);
62
+ if (!opts.dryRun) {
63
+ if (content.includes("## Conventions")) {
64
+ const insert =
65
+ `## Method version\n\n` +
66
+ `method_version: ${newVer}\n` +
67
+ `<!-- Tracks which methodology version generated this entry point -->\n` +
68
+ `<!-- Use \`agent-method status\` to compare against latest -->\n\n`;
69
+ content = content.replace("## Conventions", insert + "## Conventions");
70
+ writeFileSync(ep, content, "utf-8");
71
+ }
72
+ }
73
+ }
74
+ }
75
+
76
+ // 2. Add missing methodology files (brownfield-safe: skip existing)
77
+ const methodologyFiles = ["SESSION-LOG.md", "PROJECT-PROFILE.md"];
78
+ if (tier === "full") {
79
+ methodologyFiles.push(join(".context", "METHODOLOGY.md"));
80
+ }
81
+
82
+ for (const fname of methodologyFiles) {
83
+ const src = join(srcDir, fname);
84
+ const dst = join(d, fname);
85
+ if (existsSync(src) && !existsSync(dst)) {
86
+ actions.push(`Create ${fname} (new)`);
87
+ if (!opts.dryRun) {
88
+ mkdirSync(dirname(dst), { recursive: true });
89
+ copyFileSync(src, dst);
90
+ }
91
+ }
92
+ }
93
+
94
+ // 3. Check for session close cascade (add if missing)
95
+ if (ep) {
96
+ let content = readFileSync(ep, "utf-8");
97
+ if (!content.toLowerCase().includes("session close")) {
98
+ actions.push(`Add session close cascade rule to ${basename_of(ep)}`);
99
+ if (!opts.dryRun) {
100
+ if (content.includes("<!-- INSTRUCTION: Add project-specific cascade")) {
101
+ content = content.replace(
102
+ "<!-- INSTRUCTION: Add project-specific cascade",
103
+ "| Session close | SESSION-LOG.md (append micro-entry" +
104
+ " \u2014 workflow, features, cascades, friction, findings) " +
105
+ "|\n\n<!-- INSTRUCTION: Add project-specific cascade"
106
+ );
107
+ writeFileSync(ep, content, "utf-8");
108
+ }
109
+ }
110
+ }
111
+
112
+ // 4. Check for session observation convention
113
+ content = readFileSync(ep, "utf-8");
114
+ if (!content.toLowerCase().includes("session-log")) {
115
+ actions.push(
116
+ `Add session observation convention to ${basename_of(ep)}`
117
+ );
118
+ if (!opts.dryRun) {
119
+ if (content.includes("## Do not")) {
120
+ content = content.replace(
121
+ "## Do not",
122
+ "- At session close, append a micro-entry to " +
123
+ "SESSION-LOG.md \u2014 never skip, never read " +
124
+ "previous entries during normal work\n\n" +
125
+ "## Do not"
126
+ );
127
+ writeFileSync(ep, content, "utf-8");
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ // Report
134
+ if (actions.length === 0) {
135
+ console.log(`Project at ${directory} is up to date (v${pkg.version}).`);
136
+ } else if (opts.dryRun) {
137
+ console.log(`Dry run \u2014 ${actions.length} changes needed:`);
138
+ for (const a of actions) console.log(` - ${a}`);
139
+ } else {
140
+ console.log(
141
+ `Upgraded ${directory} to methodology v${pkg.version} (${actions.length} changes):`
142
+ );
143
+ for (const a of actions) console.log(` - ${a}`);
144
+ }
145
+ });
146
+ }
package/lib/init.js ADDED
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Interactive project setup — Node.js port of setup.sh.
3
+ *
4
+ * Creates a new methodology instance from templates with interactive
5
+ * prompts for project configuration.
6
+ */
7
+
8
+ import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, readdirSync } from "node:fs";
9
+ import { resolve, join, dirname, basename, relative } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+
15
+ const EXTENSION_MAP = {
16
+ code: "code-project.md",
17
+ data: "data-exploration.md",
18
+ analytical: "analytical-system.md",
19
+ };
20
+
21
+ const FRIENDLY_NAMES = {
22
+ code: "Code project",
23
+ data: "Data exploration",
24
+ analytical: "Analytical/prompt system",
25
+ mixed: "Multi-type project",
26
+ general: "General project",
27
+ };
28
+
29
+ /**
30
+ * Set up a new project with methodology templates.
31
+ */
32
+ export async function initProject(projectType, directory, opts = {}) {
33
+ const { tier = "starter", registryPath } = opts;
34
+ const targetDir = resolve(directory);
35
+ const methodRoot = resolve(__dirname, "..");
36
+
37
+ const templateDir = join(methodRoot, "templates", tier);
38
+ if (!existsSync(templateDir)) {
39
+ console.error(`Template directory not found: ${templateDir}`);
40
+ process.exit(1);
41
+ }
42
+
43
+ console.log(`\nSetting up ${FRIENDLY_NAMES[projectType] || projectType} project`);
44
+ console.log(` Template: ${tier}`);
45
+ console.log(` Target: ${targetDir}\n`);
46
+
47
+ // Detect brownfield (existing project with methodology files)
48
+ const isBrownfield = detectBrownfield(targetDir);
49
+ if (isBrownfield) {
50
+ console.log(" Detected existing methodology files (brownfield mode)");
51
+ console.log(" Will append to existing entry points, not overwrite.\n");
52
+ }
53
+
54
+ // Create target directory
55
+ mkdirSync(targetDir, { recursive: true });
56
+
57
+ // Copy template files
58
+ const copied = [];
59
+ const skipped = [];
60
+ copyTemplateDir(templateDir, targetDir, copied, skipped, isBrownfield);
61
+
62
+ // Apply extensions based on project type
63
+ const extensions = getExtensions(projectType);
64
+ for (const ext of extensions) {
65
+ const extFile = join(methodRoot, "templates", "extensions", ext);
66
+ if (existsSync(extFile)) {
67
+ mergeExtension(extFile, targetDir, isBrownfield);
68
+ copied.push(`extension: ${ext}`);
69
+ }
70
+ }
71
+
72
+ // Replace placeholders in entry points
73
+ replacePlaceholders(targetDir, projectType, tier);
74
+
75
+ // Report
76
+ console.log(` Files created: ${copied.length}`);
77
+ for (const f of copied) console.log(` + ${f}`);
78
+ if (skipped.length > 0) {
79
+ console.log(` Files skipped (already exist): ${skipped.length}`);
80
+ for (const f of skipped) console.log(` ~ ${f}`);
81
+ }
82
+
83
+ console.log(`\nNext steps:`);
84
+ console.log(` 1. cd ${relative(process.cwd(), targetDir) || "."}`);
85
+ console.log(` 2. Fill in PROJECT.md with your project vision`);
86
+ console.log(` 3. Delete the two entry point files you don't use`);
87
+ console.log(` (keep CLAUDE.md, .cursorrules, or AGENT.md)`);
88
+ console.log(` 4. Start a conversation — the agent reads the entry point`);
89
+ console.log(`\nRun \`agent-method check\` to validate your setup.`);
90
+ }
91
+
92
+ function detectBrownfield(dir) {
93
+ if (!existsSync(dir)) return false;
94
+ const indicators = ["CLAUDE.md", ".cursorrules", "AGENT.md", "STATE.md", ".context"];
95
+ let count = 0;
96
+ for (const ind of indicators) {
97
+ if (existsSync(join(dir, ind))) count++;
98
+ }
99
+ return count >= 2;
100
+ }
101
+
102
+ function copyTemplateDir(src, dst, copied, skipped, isBrownfield) {
103
+ if (!existsSync(src)) return;
104
+ mkdirSync(dst, { recursive: true });
105
+
106
+ for (const entry of readdirSync(src, { withFileTypes: true })) {
107
+ const srcPath = join(src, entry.name);
108
+ const dstPath = join(dst, entry.name);
109
+
110
+ if (entry.isDirectory()) {
111
+ copyTemplateDir(srcPath, dstPath, copied, skipped, isBrownfield);
112
+ } else {
113
+ // Entry points: special handling in brownfield
114
+ const isEntryPoint = ["CLAUDE.md", ".cursorrules", "AGENT.md"].includes(entry.name);
115
+
116
+ if (existsSync(dstPath)) {
117
+ if (isEntryPoint && isBrownfield) {
118
+ // Append methodology sections to existing entry point
119
+ appendToEntryPoint(srcPath, dstPath);
120
+ copied.push(`${relative(dst, dstPath)} (merged)`);
121
+ } else {
122
+ skipped.push(relative(dst, dstPath));
123
+ }
124
+ } else {
125
+ copyFileSync(srcPath, dstPath);
126
+ copied.push(relative(dst, dstPath));
127
+ }
128
+ }
129
+ }
130
+ }
131
+
132
+ function appendToEntryPoint(srcPath, dstPath) {
133
+ const srcContent = readFileSync(srcPath, "utf-8");
134
+ let dstContent = readFileSync(dstPath, "utf-8");
135
+
136
+ // Extract sections from template that don't exist in target
137
+ const sections = ["## Scoping rules", "## Dependency cascade", "## Conventions", "## Do not"];
138
+ for (const section of sections) {
139
+ if (!dstContent.includes(section) && srcContent.includes(section)) {
140
+ const idx = srcContent.indexOf(section);
141
+ // Find the next section or end
142
+ let endIdx = srcContent.length;
143
+ for (const nextSection of sections) {
144
+ const nextIdx = srcContent.indexOf(nextSection, idx + section.length);
145
+ if (nextIdx > idx && nextIdx < endIdx) {
146
+ endIdx = nextIdx;
147
+ }
148
+ }
149
+ const sectionContent = srcContent.slice(idx, endIdx).trim();
150
+ dstContent += "\n\n" + sectionContent;
151
+ }
152
+ }
153
+
154
+ writeFileSync(dstPath, dstContent, "utf-8");
155
+ }
156
+
157
+ function getExtensions(projectType) {
158
+ if (projectType === "mixed") {
159
+ return Object.values(EXTENSION_MAP);
160
+ }
161
+ const ext = EXTENSION_MAP[projectType];
162
+ return ext ? [ext] : [];
163
+ }
164
+
165
+ function mergeExtension(extFile, targetDir, isBrownfield) {
166
+ const content = readFileSync(extFile, "utf-8");
167
+
168
+ // Extract scoping rows and cascade rows from extension file
169
+ const scopingRows = [];
170
+ const cascadeRows = [];
171
+ let inScoping = false;
172
+ let inCascade = false;
173
+
174
+ for (const line of content.split("\n")) {
175
+ if (line.includes("## Scoping")) inScoping = true;
176
+ if (line.includes("## Cascade") || line.includes("## Dependency cascade"))
177
+ inCascade = true;
178
+ if (line.startsWith("## ") && !line.includes("Scoping") && !line.includes("Cascade")) {
179
+ inScoping = false;
180
+ inCascade = false;
181
+ }
182
+
183
+ if (inScoping && line.startsWith("|") && !line.startsWith("| Query") && !line.startsWith("|--")) {
184
+ scopingRows.push(line);
185
+ }
186
+ if (inCascade && line.startsWith("|") && !line.startsWith("| When") && !line.startsWith("|--")) {
187
+ cascadeRows.push(line);
188
+ }
189
+ }
190
+
191
+ // Inject rows into entry points
192
+ for (const epName of ["CLAUDE.md", ".cursorrules", "AGENT.md"]) {
193
+ const epPath = join(targetDir, epName);
194
+ if (!existsSync(epPath)) continue;
195
+
196
+ let epContent = readFileSync(epPath, "utf-8");
197
+
198
+ // Inject scoping rows before the instruction comment or at end of table
199
+ if (scopingRows.length > 0) {
200
+ const placeholder = "<!-- INSTRUCTION: Add project-specific scoping";
201
+ if (epContent.includes(placeholder)) {
202
+ epContent = epContent.replace(
203
+ placeholder,
204
+ scopingRows.join("\n") + "\n" + placeholder
205
+ );
206
+ }
207
+ }
208
+
209
+ // Inject cascade rows
210
+ if (cascadeRows.length > 0) {
211
+ const placeholder = "<!-- INSTRUCTION: Add project-specific cascade";
212
+ if (epContent.includes(placeholder)) {
213
+ epContent = epContent.replace(
214
+ placeholder,
215
+ cascadeRows.join("\n") + "\n" + placeholder
216
+ );
217
+ }
218
+ }
219
+
220
+ writeFileSync(epPath, epContent, "utf-8");
221
+ }
222
+ }
223
+
224
+ function replacePlaceholders(targetDir, projectType, tier) {
225
+ for (const epName of ["CLAUDE.md", ".cursorrules", "AGENT.md"]) {
226
+ const epPath = join(targetDir, epName);
227
+ if (!existsSync(epPath)) continue;
228
+
229
+ let content = readFileSync(epPath, "utf-8");
230
+
231
+ // Replace project type placeholder
232
+ content = content.replace(/\{your-project-type\}/g, projectType);
233
+ content = content.replace(/\{your-domain-type\}/g, projectType);
234
+
235
+ // Set template tier
236
+ content = content.replace(/template_tier:\s*\S+/, `template_tier: ${tier}`);
237
+
238
+ writeFileSync(epPath, content, "utf-8");
239
+ }
240
+ }