agent-method 1.5.3 → 1.5.5
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.
Potentially problematic release.
This version of agent-method might be problematic. Click here for more details.
- package/README.md +156 -44
- package/lib/cli/check.js +71 -71
- package/lib/cli/refine.js +202 -202
- package/lib/cli/scan.js +28 -28
- package/lib/cli/status.js +61 -61
- package/lib/cli/upgrade.js +149 -147
- package/lib/init.js +296 -296
- package/package.json +3 -2
- package/templates/README.md +11 -11
- package/templates/entry-points/.cursorrules +11 -11
- package/templates/entry-points/AGENT.md +11 -11
- package/templates/entry-points/CLAUDE.md +11 -11
- package/templates/full/.cursorrules +11 -11
- package/templates/full/AGENT.md +11 -11
- package/templates/full/CLAUDE.md +11 -11
- package/templates/full/SESSION-LOG.md +29 -0
- package/templates/starter/.cursorrules +11 -11
- package/templates/starter/AGENT.md +11 -11
- package/templates/starter/CLAUDE.md +11 -11
- package/templates/starter/SESSION-LOG.md +29 -0
package/lib/init.js
CHANGED
|
@@ -1,296 +1,296 @@
|
|
|
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, unlinkSync } 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
|
-
const RUNTIME_ENTRY_POINTS = {
|
|
30
|
-
claude: "CLAUDE.md",
|
|
31
|
-
cursor: ".cursorrules",
|
|
32
|
-
all: null, // keep all
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Set up a new project with methodology templates.
|
|
37
|
-
*/
|
|
38
|
-
export async function initProject(projectType, directory, opts = {}) {
|
|
39
|
-
const { tier = "starter", runtime = "all", profile = "standard", registryPath } = opts;
|
|
40
|
-
const targetDir = resolve(directory);
|
|
41
|
-
const methodRoot = resolve(__dirname, "..");
|
|
42
|
-
|
|
43
|
-
const templateDir = join(methodRoot, "templates", tier);
|
|
44
|
-
if (!existsSync(templateDir)) {
|
|
45
|
-
console.error(`Template directory not found: ${templateDir}`);
|
|
46
|
-
process.exit(1);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
console.log(`\nSetting up ${FRIENDLY_NAMES[projectType] || projectType} project`);
|
|
50
|
-
console.log(` Template: ${tier}`);
|
|
51
|
-
console.log(` Profile: ${profile}`);
|
|
52
|
-
console.log(` Runtime: ${runtime}`);
|
|
53
|
-
console.log(` Target: ${targetDir}\n`);
|
|
54
|
-
|
|
55
|
-
// Detect brownfield (existing project with methodology files)
|
|
56
|
-
const isBrownfield = detectBrownfield(targetDir);
|
|
57
|
-
if (isBrownfield) {
|
|
58
|
-
console.log(" Detected existing methodology files (brownfield mode)");
|
|
59
|
-
console.log(" Will append to existing entry points, not overwrite.\n");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Create target directory
|
|
63
|
-
mkdirSync(targetDir, { recursive: true });
|
|
64
|
-
|
|
65
|
-
// Copy template files
|
|
66
|
-
const copied = [];
|
|
67
|
-
const skipped = [];
|
|
68
|
-
copyTemplateDir(templateDir, targetDir, copied, skipped, isBrownfield);
|
|
69
|
-
|
|
70
|
-
// Apply extensions based on project type
|
|
71
|
-
const extensions = getExtensions(projectType);
|
|
72
|
-
for (const ext of extensions) {
|
|
73
|
-
const extFile = join(methodRoot, "templates", "extensions", ext);
|
|
74
|
-
if (existsSync(extFile)) {
|
|
75
|
-
mergeExtension(extFile, targetDir, isBrownfield);
|
|
76
|
-
copied.push(`extension: ${ext}`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Replace placeholders in entry points
|
|
81
|
-
replacePlaceholders(targetDir, projectType, tier);
|
|
82
|
-
|
|
83
|
-
// Set integration profile in entry points
|
|
84
|
-
setProfile(targetDir, profile);
|
|
85
|
-
|
|
86
|
-
// Remove unused entry points based on runtime choice
|
|
87
|
-
const removed = removeUnusedEntryPoints(targetDir, runtime, isBrownfield);
|
|
88
|
-
|
|
89
|
-
// Report
|
|
90
|
-
console.log(` Files created: ${copied.length}`);
|
|
91
|
-
for (const f of copied) console.log(` + ${f}`);
|
|
92
|
-
if (removed.length > 0) {
|
|
93
|
-
console.log(` Entry points removed: ${removed.length}`);
|
|
94
|
-
for (const f of removed) console.log(` - ${f}`);
|
|
95
|
-
}
|
|
96
|
-
if (skipped.length > 0) {
|
|
97
|
-
console.log(` Files skipped (already exist): ${skipped.length}`);
|
|
98
|
-
for (const f of skipped) console.log(` ~ ${f}`);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Determine which entry point the user kept
|
|
102
|
-
const keptEntry = runtime === "claude" ? "CLAUDE.md"
|
|
103
|
-
: runtime === "cursor" ? ".cursorrules"
|
|
104
|
-
: "CLAUDE.md / .cursorrules / AGENT.md";
|
|
105
|
-
|
|
106
|
-
console.log(`\nNext steps:`);
|
|
107
|
-
console.log(` 1. cd ${relative(process.cwd(), targetDir) || "."}`);
|
|
108
|
-
console.log(` 2. Fill in PROJECT.md with your project vision`);
|
|
109
|
-
if (runtime === "all") {
|
|
110
|
-
console.log(` 3. Delete the two entry point files you don't use`);
|
|
111
|
-
console.log(` (keep CLAUDE.md, .cursorrules, or AGENT.md)`);
|
|
112
|
-
}
|
|
113
|
-
console.log(` ${runtime === "all" ? "4" : "3"}. Start a conversation — the agent reads ${keptEntry} automatically`);
|
|
114
|
-
console.log(`\nVerify: npx
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function detectBrownfield(dir) {
|
|
118
|
-
if (!existsSync(dir)) return false;
|
|
119
|
-
const indicators = ["CLAUDE.md", ".cursorrules", "AGENT.md", "STATE.md", ".context"];
|
|
120
|
-
let count = 0;
|
|
121
|
-
for (const ind of indicators) {
|
|
122
|
-
if (existsSync(join(dir, ind))) count++;
|
|
123
|
-
}
|
|
124
|
-
return count >= 2;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function copyTemplateDir(src, dst, copied, skipped, isBrownfield) {
|
|
128
|
-
if (!existsSync(src)) return;
|
|
129
|
-
mkdirSync(dst, { recursive: true });
|
|
130
|
-
|
|
131
|
-
for (const entry of readdirSync(src, { withFileTypes: true })) {
|
|
132
|
-
const srcPath = join(src, entry.name);
|
|
133
|
-
const dstPath = join(dst, entry.name);
|
|
134
|
-
|
|
135
|
-
if (entry.isDirectory()) {
|
|
136
|
-
copyTemplateDir(srcPath, dstPath, copied, skipped, isBrownfield);
|
|
137
|
-
} else {
|
|
138
|
-
// Entry points: special handling in brownfield
|
|
139
|
-
const isEntryPoint = ["CLAUDE.md", ".cursorrules", "AGENT.md"].includes(entry.name);
|
|
140
|
-
|
|
141
|
-
if (existsSync(dstPath)) {
|
|
142
|
-
if (isEntryPoint && isBrownfield) {
|
|
143
|
-
// Append methodology sections to existing entry point
|
|
144
|
-
appendToEntryPoint(srcPath, dstPath);
|
|
145
|
-
copied.push(`${relative(dst, dstPath)} (merged)`);
|
|
146
|
-
} else {
|
|
147
|
-
skipped.push(relative(dst, dstPath));
|
|
148
|
-
}
|
|
149
|
-
} else {
|
|
150
|
-
copyFileSync(srcPath, dstPath);
|
|
151
|
-
copied.push(relative(dst, dstPath));
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function appendToEntryPoint(srcPath, dstPath) {
|
|
158
|
-
const srcContent = readFileSync(srcPath, "utf-8");
|
|
159
|
-
let dstContent = readFileSync(dstPath, "utf-8");
|
|
160
|
-
|
|
161
|
-
// Extract sections from template that don't exist in target
|
|
162
|
-
const sections = ["## Scoping rules", "## Dependency cascade", "## Conventions", "## Do not"];
|
|
163
|
-
for (const section of sections) {
|
|
164
|
-
if (!dstContent.includes(section) && srcContent.includes(section)) {
|
|
165
|
-
const idx = srcContent.indexOf(section);
|
|
166
|
-
// Find the next section or end
|
|
167
|
-
let endIdx = srcContent.length;
|
|
168
|
-
for (const nextSection of sections) {
|
|
169
|
-
const nextIdx = srcContent.indexOf(nextSection, idx + section.length);
|
|
170
|
-
if (nextIdx > idx && nextIdx < endIdx) {
|
|
171
|
-
endIdx = nextIdx;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
const sectionContent = srcContent.slice(idx, endIdx).trim();
|
|
175
|
-
dstContent += "\n\n" + sectionContent;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
writeFileSync(dstPath, dstContent, "utf-8");
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function getExtensions(projectType) {
|
|
183
|
-
if (projectType === "mixed") {
|
|
184
|
-
return Object.values(EXTENSION_MAP);
|
|
185
|
-
}
|
|
186
|
-
const ext = EXTENSION_MAP[projectType];
|
|
187
|
-
return ext ? [ext] : [];
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function mergeExtension(extFile, targetDir, isBrownfield) {
|
|
191
|
-
const content = readFileSync(extFile, "utf-8");
|
|
192
|
-
|
|
193
|
-
// Extract scoping rows and cascade rows from extension file
|
|
194
|
-
const scopingRows = [];
|
|
195
|
-
const cascadeRows = [];
|
|
196
|
-
let inScoping = false;
|
|
197
|
-
let inCascade = false;
|
|
198
|
-
|
|
199
|
-
for (const line of content.split("\n")) {
|
|
200
|
-
if (line.includes("## Scoping")) inScoping = true;
|
|
201
|
-
if (line.includes("## Cascade") || line.includes("## Dependency cascade"))
|
|
202
|
-
inCascade = true;
|
|
203
|
-
if (line.startsWith("## ") && !line.includes("Scoping") && !line.includes("Cascade")) {
|
|
204
|
-
inScoping = false;
|
|
205
|
-
inCascade = false;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (inScoping && line.startsWith("|") && !line.startsWith("| Query") && !line.startsWith("|--")) {
|
|
209
|
-
scopingRows.push(line);
|
|
210
|
-
}
|
|
211
|
-
if (inCascade && line.startsWith("|") && !line.startsWith("| When") && !line.startsWith("|--")) {
|
|
212
|
-
cascadeRows.push(line);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Inject rows into entry points
|
|
217
|
-
for (const epName of ["CLAUDE.md", ".cursorrules", "AGENT.md"]) {
|
|
218
|
-
const epPath = join(targetDir, epName);
|
|
219
|
-
if (!existsSync(epPath)) continue;
|
|
220
|
-
|
|
221
|
-
let epContent = readFileSync(epPath, "utf-8");
|
|
222
|
-
|
|
223
|
-
// Inject scoping rows before the instruction comment or at end of table
|
|
224
|
-
if (scopingRows.length > 0) {
|
|
225
|
-
const placeholder = "<!-- INSTRUCTION: Add project-specific scoping";
|
|
226
|
-
if (epContent.includes(placeholder)) {
|
|
227
|
-
epContent = epContent.replace(
|
|
228
|
-
placeholder,
|
|
229
|
-
scopingRows.join("\n") + "\n" + placeholder
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Inject cascade rows
|
|
235
|
-
if (cascadeRows.length > 0) {
|
|
236
|
-
const placeholder = "<!-- INSTRUCTION: Add project-specific cascade";
|
|
237
|
-
if (epContent.includes(placeholder)) {
|
|
238
|
-
epContent = epContent.replace(
|
|
239
|
-
placeholder,
|
|
240
|
-
cascadeRows.join("\n") + "\n" + placeholder
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
writeFileSync(epPath, epContent, "utf-8");
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function replacePlaceholders(targetDir, projectType, tier) {
|
|
250
|
-
for (const epName of ["CLAUDE.md", ".cursorrules", "AGENT.md"]) {
|
|
251
|
-
const epPath = join(targetDir, epName);
|
|
252
|
-
if (!existsSync(epPath)) continue;
|
|
253
|
-
|
|
254
|
-
let content = readFileSync(epPath, "utf-8");
|
|
255
|
-
|
|
256
|
-
// Replace project type placeholder
|
|
257
|
-
content = content.replace(/\{your-project-type\}/g, projectType);
|
|
258
|
-
content = content.replace(/\{your-domain-type\}/g, projectType);
|
|
259
|
-
|
|
260
|
-
// Set template tier
|
|
261
|
-
content = content.replace(/template_tier:\s*\S+/, `template_tier: ${tier}`);
|
|
262
|
-
|
|
263
|
-
writeFileSync(epPath, content, "utf-8");
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function setProfile(targetDir, profile) {
|
|
268
|
-
for (const epName of ["CLAUDE.md", ".cursorrules", "AGENT.md"]) {
|
|
269
|
-
const epPath = join(targetDir, epName);
|
|
270
|
-
if (!existsSync(epPath)) continue;
|
|
271
|
-
|
|
272
|
-
let content = readFileSync(epPath, "utf-8");
|
|
273
|
-
content = content.replace(/tier:\s*(lite|standard|full)/, `tier: ${profile}`);
|
|
274
|
-
writeFileSync(epPath, content, "utf-8");
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function removeUnusedEntryPoints(targetDir, runtime, isBrownfield) {
|
|
279
|
-
if (runtime === "all" || isBrownfield) return [];
|
|
280
|
-
|
|
281
|
-
const allEntryPoints = ["CLAUDE.md", ".cursorrules", "AGENT.md"];
|
|
282
|
-
const keep = RUNTIME_ENTRY_POINTS[runtime];
|
|
283
|
-
if (!keep) return [];
|
|
284
|
-
|
|
285
|
-
const removed = [];
|
|
286
|
-
for (const ep of allEntryPoints) {
|
|
287
|
-
if (ep !== keep) {
|
|
288
|
-
const epPath = join(targetDir, ep);
|
|
289
|
-
if (existsSync(epPath)) {
|
|
290
|
-
unlinkSync(epPath);
|
|
291
|
-
removed.push(ep);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
return removed;
|
|
296
|
-
}
|
|
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, unlinkSync } 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
|
+
const RUNTIME_ENTRY_POINTS = {
|
|
30
|
+
claude: "CLAUDE.md",
|
|
31
|
+
cursor: ".cursorrules",
|
|
32
|
+
all: null, // keep all
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Set up a new project with methodology templates.
|
|
37
|
+
*/
|
|
38
|
+
export async function initProject(projectType, directory, opts = {}) {
|
|
39
|
+
const { tier = "starter", runtime = "all", profile = "standard", registryPath } = opts;
|
|
40
|
+
const targetDir = resolve(directory);
|
|
41
|
+
const methodRoot = resolve(__dirname, "..");
|
|
42
|
+
|
|
43
|
+
const templateDir = join(methodRoot, "templates", tier);
|
|
44
|
+
if (!existsSync(templateDir)) {
|
|
45
|
+
console.error(`Template directory not found: ${templateDir}`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log(`\nSetting up ${FRIENDLY_NAMES[projectType] || projectType} project`);
|
|
50
|
+
console.log(` Template: ${tier}`);
|
|
51
|
+
console.log(` Profile: ${profile}`);
|
|
52
|
+
console.log(` Runtime: ${runtime}`);
|
|
53
|
+
console.log(` Target: ${targetDir}\n`);
|
|
54
|
+
|
|
55
|
+
// Detect brownfield (existing project with methodology files)
|
|
56
|
+
const isBrownfield = detectBrownfield(targetDir);
|
|
57
|
+
if (isBrownfield) {
|
|
58
|
+
console.log(" Detected existing methodology files (brownfield mode)");
|
|
59
|
+
console.log(" Will append to existing entry points, not overwrite.\n");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Create target directory
|
|
63
|
+
mkdirSync(targetDir, { recursive: true });
|
|
64
|
+
|
|
65
|
+
// Copy template files
|
|
66
|
+
const copied = [];
|
|
67
|
+
const skipped = [];
|
|
68
|
+
copyTemplateDir(templateDir, targetDir, copied, skipped, isBrownfield);
|
|
69
|
+
|
|
70
|
+
// Apply extensions based on project type
|
|
71
|
+
const extensions = getExtensions(projectType);
|
|
72
|
+
for (const ext of extensions) {
|
|
73
|
+
const extFile = join(methodRoot, "templates", "extensions", ext);
|
|
74
|
+
if (existsSync(extFile)) {
|
|
75
|
+
mergeExtension(extFile, targetDir, isBrownfield);
|
|
76
|
+
copied.push(`extension: ${ext}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Replace placeholders in entry points
|
|
81
|
+
replacePlaceholders(targetDir, projectType, tier);
|
|
82
|
+
|
|
83
|
+
// Set integration profile in entry points
|
|
84
|
+
setProfile(targetDir, profile);
|
|
85
|
+
|
|
86
|
+
// Remove unused entry points based on runtime choice
|
|
87
|
+
const removed = removeUnusedEntryPoints(targetDir, runtime, isBrownfield);
|
|
88
|
+
|
|
89
|
+
// Report
|
|
90
|
+
console.log(` Files created: ${copied.length}`);
|
|
91
|
+
for (const f of copied) console.log(` + ${f}`);
|
|
92
|
+
if (removed.length > 0) {
|
|
93
|
+
console.log(` Entry points removed: ${removed.length}`);
|
|
94
|
+
for (const f of removed) console.log(` - ${f}`);
|
|
95
|
+
}
|
|
96
|
+
if (skipped.length > 0) {
|
|
97
|
+
console.log(` Files skipped (already exist): ${skipped.length}`);
|
|
98
|
+
for (const f of skipped) console.log(` ~ ${f}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Determine which entry point the user kept
|
|
102
|
+
const keptEntry = runtime === "claude" ? "CLAUDE.md"
|
|
103
|
+
: runtime === "cursor" ? ".cursorrules"
|
|
104
|
+
: "CLAUDE.md / .cursorrules / AGENT.md";
|
|
105
|
+
|
|
106
|
+
console.log(`\nNext steps:`);
|
|
107
|
+
console.log(` 1. cd ${relative(process.cwd(), targetDir) || "."}`);
|
|
108
|
+
console.log(` 2. Fill in PROJECT.md with your project vision`);
|
|
109
|
+
if (runtime === "all") {
|
|
110
|
+
console.log(` 3. Delete the two entry point files you don't use`);
|
|
111
|
+
console.log(` (keep CLAUDE.md, .cursorrules, or AGENT.md)`);
|
|
112
|
+
}
|
|
113
|
+
console.log(` ${runtime === "all" ? "4" : "3"}. Start a conversation — the agent reads ${keptEntry} automatically`);
|
|
114
|
+
console.log(`\nVerify: npx agent-method check`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function detectBrownfield(dir) {
|
|
118
|
+
if (!existsSync(dir)) return false;
|
|
119
|
+
const indicators = ["CLAUDE.md", ".cursorrules", "AGENT.md", "STATE.md", ".context"];
|
|
120
|
+
let count = 0;
|
|
121
|
+
for (const ind of indicators) {
|
|
122
|
+
if (existsSync(join(dir, ind))) count++;
|
|
123
|
+
}
|
|
124
|
+
return count >= 2;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function copyTemplateDir(src, dst, copied, skipped, isBrownfield) {
|
|
128
|
+
if (!existsSync(src)) return;
|
|
129
|
+
mkdirSync(dst, { recursive: true });
|
|
130
|
+
|
|
131
|
+
for (const entry of readdirSync(src, { withFileTypes: true })) {
|
|
132
|
+
const srcPath = join(src, entry.name);
|
|
133
|
+
const dstPath = join(dst, entry.name);
|
|
134
|
+
|
|
135
|
+
if (entry.isDirectory()) {
|
|
136
|
+
copyTemplateDir(srcPath, dstPath, copied, skipped, isBrownfield);
|
|
137
|
+
} else {
|
|
138
|
+
// Entry points: special handling in brownfield
|
|
139
|
+
const isEntryPoint = ["CLAUDE.md", ".cursorrules", "AGENT.md"].includes(entry.name);
|
|
140
|
+
|
|
141
|
+
if (existsSync(dstPath)) {
|
|
142
|
+
if (isEntryPoint && isBrownfield) {
|
|
143
|
+
// Append methodology sections to existing entry point
|
|
144
|
+
appendToEntryPoint(srcPath, dstPath);
|
|
145
|
+
copied.push(`${relative(dst, dstPath)} (merged)`);
|
|
146
|
+
} else {
|
|
147
|
+
skipped.push(relative(dst, dstPath));
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
copyFileSync(srcPath, dstPath);
|
|
151
|
+
copied.push(relative(dst, dstPath));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function appendToEntryPoint(srcPath, dstPath) {
|
|
158
|
+
const srcContent = readFileSync(srcPath, "utf-8");
|
|
159
|
+
let dstContent = readFileSync(dstPath, "utf-8");
|
|
160
|
+
|
|
161
|
+
// Extract sections from template that don't exist in target
|
|
162
|
+
const sections = ["## Scoping rules", "## Dependency cascade", "## Conventions", "## Do not"];
|
|
163
|
+
for (const section of sections) {
|
|
164
|
+
if (!dstContent.includes(section) && srcContent.includes(section)) {
|
|
165
|
+
const idx = srcContent.indexOf(section);
|
|
166
|
+
// Find the next section or end
|
|
167
|
+
let endIdx = srcContent.length;
|
|
168
|
+
for (const nextSection of sections) {
|
|
169
|
+
const nextIdx = srcContent.indexOf(nextSection, idx + section.length);
|
|
170
|
+
if (nextIdx > idx && nextIdx < endIdx) {
|
|
171
|
+
endIdx = nextIdx;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const sectionContent = srcContent.slice(idx, endIdx).trim();
|
|
175
|
+
dstContent += "\n\n" + sectionContent;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
writeFileSync(dstPath, dstContent, "utf-8");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function getExtensions(projectType) {
|
|
183
|
+
if (projectType === "mixed") {
|
|
184
|
+
return Object.values(EXTENSION_MAP);
|
|
185
|
+
}
|
|
186
|
+
const ext = EXTENSION_MAP[projectType];
|
|
187
|
+
return ext ? [ext] : [];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function mergeExtension(extFile, targetDir, isBrownfield) {
|
|
191
|
+
const content = readFileSync(extFile, "utf-8");
|
|
192
|
+
|
|
193
|
+
// Extract scoping rows and cascade rows from extension file
|
|
194
|
+
const scopingRows = [];
|
|
195
|
+
const cascadeRows = [];
|
|
196
|
+
let inScoping = false;
|
|
197
|
+
let inCascade = false;
|
|
198
|
+
|
|
199
|
+
for (const line of content.split("\n")) {
|
|
200
|
+
if (line.includes("## Scoping")) inScoping = true;
|
|
201
|
+
if (line.includes("## Cascade") || line.includes("## Dependency cascade"))
|
|
202
|
+
inCascade = true;
|
|
203
|
+
if (line.startsWith("## ") && !line.includes("Scoping") && !line.includes("Cascade")) {
|
|
204
|
+
inScoping = false;
|
|
205
|
+
inCascade = false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (inScoping && line.startsWith("|") && !line.startsWith("| Query") && !line.startsWith("|--")) {
|
|
209
|
+
scopingRows.push(line);
|
|
210
|
+
}
|
|
211
|
+
if (inCascade && line.startsWith("|") && !line.startsWith("| When") && !line.startsWith("|--")) {
|
|
212
|
+
cascadeRows.push(line);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Inject rows into entry points
|
|
217
|
+
for (const epName of ["CLAUDE.md", ".cursorrules", "AGENT.md"]) {
|
|
218
|
+
const epPath = join(targetDir, epName);
|
|
219
|
+
if (!existsSync(epPath)) continue;
|
|
220
|
+
|
|
221
|
+
let epContent = readFileSync(epPath, "utf-8");
|
|
222
|
+
|
|
223
|
+
// Inject scoping rows before the instruction comment or at end of table
|
|
224
|
+
if (scopingRows.length > 0) {
|
|
225
|
+
const placeholder = "<!-- INSTRUCTION: Add project-specific scoping";
|
|
226
|
+
if (epContent.includes(placeholder)) {
|
|
227
|
+
epContent = epContent.replace(
|
|
228
|
+
placeholder,
|
|
229
|
+
scopingRows.join("\n") + "\n" + placeholder
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Inject cascade rows
|
|
235
|
+
if (cascadeRows.length > 0) {
|
|
236
|
+
const placeholder = "<!-- INSTRUCTION: Add project-specific cascade";
|
|
237
|
+
if (epContent.includes(placeholder)) {
|
|
238
|
+
epContent = epContent.replace(
|
|
239
|
+
placeholder,
|
|
240
|
+
cascadeRows.join("\n") + "\n" + placeholder
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
writeFileSync(epPath, epContent, "utf-8");
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function replacePlaceholders(targetDir, projectType, tier) {
|
|
250
|
+
for (const epName of ["CLAUDE.md", ".cursorrules", "AGENT.md"]) {
|
|
251
|
+
const epPath = join(targetDir, epName);
|
|
252
|
+
if (!existsSync(epPath)) continue;
|
|
253
|
+
|
|
254
|
+
let content = readFileSync(epPath, "utf-8");
|
|
255
|
+
|
|
256
|
+
// Replace project type placeholder
|
|
257
|
+
content = content.replace(/\{your-project-type\}/g, projectType);
|
|
258
|
+
content = content.replace(/\{your-domain-type\}/g, projectType);
|
|
259
|
+
|
|
260
|
+
// Set template tier
|
|
261
|
+
content = content.replace(/template_tier:\s*\S+/, `template_tier: ${tier}`);
|
|
262
|
+
|
|
263
|
+
writeFileSync(epPath, content, "utf-8");
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function setProfile(targetDir, profile) {
|
|
268
|
+
for (const epName of ["CLAUDE.md", ".cursorrules", "AGENT.md"]) {
|
|
269
|
+
const epPath = join(targetDir, epName);
|
|
270
|
+
if (!existsSync(epPath)) continue;
|
|
271
|
+
|
|
272
|
+
let content = readFileSync(epPath, "utf-8");
|
|
273
|
+
content = content.replace(/tier:\s*(lite|standard|full)/, `tier: ${profile}`);
|
|
274
|
+
writeFileSync(epPath, content, "utf-8");
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function removeUnusedEntryPoints(targetDir, runtime, isBrownfield) {
|
|
279
|
+
if (runtime === "all" || isBrownfield) return [];
|
|
280
|
+
|
|
281
|
+
const allEntryPoints = ["CLAUDE.md", ".cursorrules", "AGENT.md"];
|
|
282
|
+
const keep = RUNTIME_ENTRY_POINTS[runtime];
|
|
283
|
+
if (!keep) return [];
|
|
284
|
+
|
|
285
|
+
const removed = [];
|
|
286
|
+
for (const ep of allEntryPoints) {
|
|
287
|
+
if (ep !== keep) {
|
|
288
|
+
const epPath = join(targetDir, ep);
|
|
289
|
+
if (existsSync(epPath)) {
|
|
290
|
+
unlinkSync(epPath);
|
|
291
|
+
removed.push(ep);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return removed;
|
|
296
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-method",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.5",
|
|
4
4
|
"description": "CLI tools for the wwa methodology — registry-driven routing, validation, and project setup for AI-agent-assisted development",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"author": "wwa contributors",
|
|
16
16
|
"bin": {
|
|
17
|
-
"wwa": "bin/wwa.js"
|
|
17
|
+
"wwa": "bin/wwa.js",
|
|
18
|
+
"agent-method": "bin/wwa.js"
|
|
18
19
|
},
|
|
19
20
|
"files": [
|
|
20
21
|
"bin/",
|
package/templates/README.md
CHANGED
|
@@ -229,7 +229,7 @@ See `docs/architecture/context-pairing.md` for the full context maintenance life
|
|
|
229
229
|
### Recommended: npx
|
|
230
230
|
|
|
231
231
|
```bash
|
|
232
|
-
npx
|
|
232
|
+
npx agent-method init code ~/my-project
|
|
233
233
|
```
|
|
234
234
|
|
|
235
235
|
Replace `code` with: `context`, `data`, `mix`, or `general`.
|
|
@@ -249,8 +249,8 @@ See `docs/guides/quick-start.md` for a detailed walkthrough.
|
|
|
249
249
|
The methodology works without any tooling. For teams that want additional validation and automation:
|
|
250
250
|
|
|
251
251
|
```bash
|
|
252
|
-
npx
|
|
253
|
-
npm install -g
|
|
252
|
+
npx agent-method # zero-install (Node.js 18+)
|
|
253
|
+
npm install -g agent-method # permanent install
|
|
254
254
|
pip install wwa-tools # Python alternative
|
|
255
255
|
```
|
|
256
256
|
|
|
@@ -258,13 +258,13 @@ pip install wwa-tools # Python alternative
|
|
|
258
258
|
|
|
259
259
|
| Command | Use case |
|
|
260
260
|
|---------|----------|
|
|
261
|
-
| `npx
|
|
262
|
-
| `npx
|
|
263
|
-
| `npx
|
|
264
|
-
| `npx
|
|
265
|
-
| `npx
|
|
266
|
-
| `npx
|
|
267
|
-
| `npx
|
|
261
|
+
| `npx agent-method check` | Validate entry point (auto-finds CLAUDE.md/.cursorrules/AGENT.md) |
|
|
262
|
+
| `npx agent-method scan` | Detect project type from directory contents |
|
|
263
|
+
| `npx agent-method route "<query>"` | Test how a query routes through the pipeline |
|
|
264
|
+
| `npx agent-method refine` | Extract refinement report (auto-finds SESSION-LOG.md) |
|
|
265
|
+
| `npx agent-method status` | Check if methodology version is current |
|
|
266
|
+
| `npx agent-method upgrade` | Brownfield-safe update (adds missing files, updates version) |
|
|
267
|
+
| `npx agent-method init <type>` | Describe entry point contents for a project type |
|
|
268
268
|
|
|
269
269
|
### Project types
|
|
270
270
|
|
|
@@ -279,7 +279,7 @@ wwa init mix # multi-type project
|
|
|
279
279
|
|
|
280
280
|
### Advanced: pipeline subcommands
|
|
281
281
|
|
|
282
|
-
For debugging routing logic: `npx
|
|
282
|
+
For debugging routing logic: `npx agent-method pipeline classify|select|resolve|cascade|test`.
|
|
283
283
|
|
|
284
284
|
### Dependencies
|
|
285
285
|
|