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/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 +47 -36
- package/lib/init.js +1 -2
- package/lib/mcp-server.js +842 -1
- package/package.json +1 -1
package/lib/cli/add.js
CHANGED
|
@@ -24,25 +24,172 @@ function readStdin() {
|
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Core implementation for `wwa add` and MCP write-safe tools.
|
|
29
|
+
* Returns a structured result instead of printing or exiting.
|
|
30
|
+
*/
|
|
31
|
+
export async function runAdd(type, content, options = {}) {
|
|
32
|
+
const dir = resolve(options.directory || ".");
|
|
33
|
+
const dryRun = options.dryRun || false;
|
|
34
|
+
|
|
35
|
+
if (!ADD_TYPES.includes(type)) {
|
|
36
|
+
const error = new Error(
|
|
37
|
+
`Unknown type: ${type}. Use one of: ${ADD_TYPES.join(", ")}`
|
|
38
|
+
);
|
|
39
|
+
error.code = "INVALID_TYPE";
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!content || !content.trim()) {
|
|
44
|
+
const error = new Error("Content is required for add operation.");
|
|
45
|
+
error.code = "NO_CONTENT";
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let targetPath;
|
|
50
|
+
let toAppend;
|
|
51
|
+
|
|
52
|
+
switch (type) {
|
|
53
|
+
case "backlog": {
|
|
54
|
+
targetPath = join(dir, "todos", "backlog.md");
|
|
55
|
+
if (!existsSync(join(dir, "todos"))) {
|
|
56
|
+
targetPath = join(dir, "backlog.md");
|
|
57
|
+
}
|
|
58
|
+
if (!existsSync(targetPath)) {
|
|
59
|
+
targetPath = join(dir, "todos", "backlog.md");
|
|
60
|
+
}
|
|
61
|
+
const title = content.includes(" — ") ? content.split(" — ")[0].trim() : content;
|
|
62
|
+
const rest = content.includes(" — ")
|
|
63
|
+
? content.split(" — ").slice(1).join(" — ").trim()
|
|
64
|
+
: "";
|
|
65
|
+
toAppend = `\n- [ ] **${title}**${rest ? ` — ${rest}` : ""}\n`;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case "decision": {
|
|
69
|
+
targetPath = join(dir, "STATE.md");
|
|
70
|
+
const parts = content.includes(" | ")
|
|
71
|
+
? content.split(" | ").map((s) => s.trim())
|
|
72
|
+
: [content, ""];
|
|
73
|
+
const decision = parts[0];
|
|
74
|
+
const rationale = parts[1] || "";
|
|
75
|
+
toAppend = `\n| ${today()} | ${decision} | ${rationale} |\n`;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case "finding": {
|
|
79
|
+
const reviewDir = join(dir, "docs", "internal", "review");
|
|
80
|
+
const findingsPath = join(reviewDir, "findings.md");
|
|
81
|
+
const fallbackPath = join(reviewDir, "installation-improvements.md");
|
|
82
|
+
if (existsSync(findingsPath)) {
|
|
83
|
+
targetPath = findingsPath;
|
|
84
|
+
} else if (existsSync(fallbackPath)) {
|
|
85
|
+
targetPath = fallbackPath;
|
|
86
|
+
} else {
|
|
87
|
+
targetPath = findingsPath;
|
|
88
|
+
if (!existsSync(join(dir, "docs", "internal", "review"))) {
|
|
89
|
+
const alt = join(dir, "agentWorkflows", "observations.md");
|
|
90
|
+
targetPath = existsSync(join(dir, "agentWorkflows")) ? alt : findingsPath;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
toAppend = `\n### ${today()}\n\n- ${content}\n\n`;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case "session": {
|
|
97
|
+
const sessionLog = findSessionLog(dir) || join(dir, "SESSION-LOG.md");
|
|
98
|
+
targetPath = sessionLog;
|
|
99
|
+
toAppend = content.endsWith("\n") ? content : content + "\n";
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case "summary": {
|
|
103
|
+
targetPath = join(dir, "SUMMARY.md");
|
|
104
|
+
toAppend = content.endsWith("\n") ? content : content + "\n";
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
default: {
|
|
108
|
+
const error = new Error(`Unsupported add type: ${type}`);
|
|
109
|
+
error.code = "INVALID_TYPE";
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!isSafeToWrite(targetPath)) {
|
|
115
|
+
const error = new Error(
|
|
116
|
+
`Safety: refusing to write to ${targetPath} (only .md, .yaml, .yml allowed).`
|
|
117
|
+
);
|
|
118
|
+
error.code = "UNSAFE_TARGET";
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (dryRun) {
|
|
123
|
+
return {
|
|
124
|
+
directory: dir,
|
|
125
|
+
type,
|
|
126
|
+
target: targetPath,
|
|
127
|
+
content: toAppend,
|
|
128
|
+
dryRun: true,
|
|
129
|
+
wrote: false,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const dirToEnsure = resolve(targetPath, "..");
|
|
134
|
+
if (!existsSync(dirToEnsure)) {
|
|
135
|
+
const { mkdirSync } = await import("node:fs");
|
|
136
|
+
mkdirSync(dirToEnsure, { recursive: true });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!existsSync(targetPath)) {
|
|
140
|
+
if (type !== "finding") {
|
|
141
|
+
const error = new Error(
|
|
142
|
+
`File not found: ${targetPath}. Create it first or run from project root.`
|
|
143
|
+
);
|
|
144
|
+
error.code = "MISSING_TARGET";
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
// For findings, create a new file with header.
|
|
148
|
+
safeWriteFile(
|
|
149
|
+
targetPath,
|
|
150
|
+
`# Findings\n\n<!-- Appended by wwa add finding -->\n${toAppend}`
|
|
151
|
+
);
|
|
152
|
+
remindCascade(dir);
|
|
153
|
+
return {
|
|
154
|
+
directory: dir,
|
|
155
|
+
type,
|
|
156
|
+
target: targetPath,
|
|
157
|
+
content: toAppend,
|
|
158
|
+
dryRun: false,
|
|
159
|
+
wrote: true,
|
|
160
|
+
createdFile: true,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const existing = readFileSync(targetPath, "utf-8");
|
|
165
|
+
safeWriteFile(targetPath, existing + toAppend);
|
|
166
|
+
remindCascade(dir);
|
|
167
|
+
return {
|
|
168
|
+
directory: dir,
|
|
169
|
+
type,
|
|
170
|
+
target: targetPath,
|
|
171
|
+
content: toAppend,
|
|
172
|
+
dryRun: false,
|
|
173
|
+
wrote: true,
|
|
174
|
+
createdFile: false,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
27
178
|
export function register(program) {
|
|
28
179
|
program
|
|
29
180
|
.command("add <type> [content...]")
|
|
30
|
-
.description(
|
|
181
|
+
.description(
|
|
182
|
+
"Append content to methodology files (backlog, decision, finding, session, summary)"
|
|
183
|
+
)
|
|
31
184
|
.option("-d, --directory <path>", "Project directory", ".")
|
|
32
185
|
.option("--file <path>", "Read content from file (for session/summary)")
|
|
33
186
|
.option("--dry-run", "Show what would be written without writing")
|
|
34
187
|
.option("--json", "Output target path and content as JSON")
|
|
35
188
|
.action(async (type, contentParts, opts) => {
|
|
36
189
|
const dir = resolve(opts.directory);
|
|
37
|
-
const dryRun = opts.dryRun || false;
|
|
38
190
|
const asJson = opts.json || false;
|
|
39
|
-
|
|
40
|
-
if (!ADD_TYPES.includes(type)) {
|
|
41
|
-
console.error(`Unknown type: ${type}. Use one of: ${ADD_TYPES.join(", ")}`);
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
191
|
let content;
|
|
192
|
+
|
|
46
193
|
if (opts.file) {
|
|
47
194
|
const fpath = resolve(opts.file);
|
|
48
195
|
if (!existsSync(fpath)) {
|
|
@@ -63,101 +210,28 @@ export function register(program) {
|
|
|
63
210
|
process.exit(1);
|
|
64
211
|
}
|
|
65
212
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
targetPath = join(dir, "todos", "backlog.md");
|
|
72
|
-
if (!existsSync(join(dir, "todos"))) {
|
|
73
|
-
targetPath = join(dir, "backlog.md");
|
|
74
|
-
}
|
|
75
|
-
if (!existsSync(targetPath)) {
|
|
76
|
-
targetPath = join(dir, "todos", "backlog.md");
|
|
77
|
-
}
|
|
78
|
-
const title = content.includes(" — ") ? content.split(" — ")[0].trim() : content;
|
|
79
|
-
const rest = content.includes(" — ") ? content.split(" — ").slice(1).join(" — ").trim() : "";
|
|
80
|
-
toAppend = `\n- [ ] **${title}**${rest ? ` — ${rest}` : ""}\n`;
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
case "decision": {
|
|
84
|
-
targetPath = join(dir, "STATE.md");
|
|
85
|
-
const parts = content.includes(" | ") ? content.split(" | ").map((s) => s.trim()) : [content, ""];
|
|
86
|
-
const decision = parts[0];
|
|
87
|
-
const rationale = parts[1] || "";
|
|
88
|
-
toAppend = `\n| ${today()} | ${decision} | ${rationale} |\n`;
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
case "finding": {
|
|
92
|
-
const reviewDir = join(dir, "docs", "internal", "review");
|
|
93
|
-
const findingsPath = join(reviewDir, "findings.md");
|
|
94
|
-
const fallbackPath = join(reviewDir, "installation-improvements.md");
|
|
95
|
-
if (existsSync(findingsPath)) {
|
|
96
|
-
targetPath = findingsPath;
|
|
97
|
-
} else if (existsSync(fallbackPath)) {
|
|
98
|
-
targetPath = fallbackPath;
|
|
99
|
-
} else {
|
|
100
|
-
targetPath = findingsPath;
|
|
101
|
-
if (!existsSync(join(dir, "docs", "internal", "review"))) {
|
|
102
|
-
const alt = join(dir, "agentWorkflows", "observations.md");
|
|
103
|
-
targetPath = existsSync(join(dir, "agentWorkflows")) ? alt : findingsPath;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
toAppend = `\n### ${today()}\n\n- ${content}\n\n`;
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
case "session": {
|
|
110
|
-
const sessionLog = findSessionLog(dir) || join(dir, "SESSION-LOG.md");
|
|
111
|
-
targetPath = sessionLog;
|
|
112
|
-
toAppend = content.endsWith("\n") ? content : content + "\n";
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
case "summary": {
|
|
116
|
-
targetPath = join(dir, "SUMMARY.md");
|
|
117
|
-
toAppend = content.endsWith("\n") ? content : content + "\n";
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
default:
|
|
121
|
-
process.exit(1);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (!isSafeToWrite(targetPath)) {
|
|
125
|
-
console.error(`Safety: refusing to write to ${targetPath} (only .md, .yaml, .yml allowed).`);
|
|
126
|
-
process.exit(1);
|
|
127
|
-
}
|
|
213
|
+
try {
|
|
214
|
+
const result = await runAdd(type, content, {
|
|
215
|
+
directory: dir,
|
|
216
|
+
dryRun: opts.dryRun || false,
|
|
217
|
+
});
|
|
128
218
|
|
|
129
|
-
if (dryRun || asJson) {
|
|
130
219
|
if (asJson) {
|
|
131
|
-
console.log(JSON.stringify(
|
|
132
|
-
|
|
133
|
-
console.log("Target:", targetPath);
|
|
134
|
-
console.log("Content to append:");
|
|
135
|
-
console.log(toAppend);
|
|
220
|
+
console.log(JSON.stringify(result, null, 2));
|
|
221
|
+
return;
|
|
136
222
|
}
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
223
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (!existsSync(targetPath)) {
|
|
147
|
-
if (type === "finding") {
|
|
148
|
-
safeWriteFile(targetPath, `# Findings\n\n<!-- Appended by wwa add finding -->\n${toAppend}`);
|
|
149
|
-
console.log("Appended to", targetPath);
|
|
150
|
-
remindCascade(dir);
|
|
151
|
-
return;
|
|
224
|
+
if (result.dryRun) {
|
|
225
|
+
console.log("Target:", result.target);
|
|
226
|
+
console.log("Content to append:");
|
|
227
|
+
console.log(result.content);
|
|
228
|
+
} else {
|
|
229
|
+
console.log("Appended to", result.target);
|
|
152
230
|
}
|
|
153
|
-
|
|
231
|
+
} catch (e) {
|
|
232
|
+
console.error(e.message);
|
|
154
233
|
process.exit(1);
|
|
155
234
|
}
|
|
156
|
-
|
|
157
|
-
const existing = readFileSync(targetPath, "utf-8");
|
|
158
|
-
safeWriteFile(targetPath, existing + toAppend);
|
|
159
|
-
console.log("Appended to", targetPath);
|
|
160
|
-
remindCascade(dir);
|
|
161
235
|
});
|
|
162
236
|
}
|
|
163
237
|
|
package/lib/cli/casestudy.js
CHANGED
|
@@ -15,6 +15,133 @@ import { resolveTokensPath, resolveDocsMapPath } from "../boundaries.js";
|
|
|
15
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
16
|
const __dirname = dirname(__filename);
|
|
17
17
|
|
|
18
|
+
export async function runCasestudy(directory, opts = {}) {
|
|
19
|
+
const dir = resolve(directory || ".");
|
|
20
|
+
const options = {
|
|
21
|
+
output: opts.output || null,
|
|
22
|
+
name: opts.name || null,
|
|
23
|
+
json: !!opts.json,
|
|
24
|
+
yamlOnly: !!opts.yamlOnly,
|
|
25
|
+
internalRegistry: !!opts.internalRegistry,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Gather all project data
|
|
29
|
+
const data = await gatherProjectData(dir, options.name);
|
|
30
|
+
|
|
31
|
+
// Load internal registry if requested
|
|
32
|
+
if (options.internalRegistry) {
|
|
33
|
+
const { loadInternalRegistry } = await import("./doc-review.js");
|
|
34
|
+
const registry = await loadInternalRegistry(dir);
|
|
35
|
+
if (registry) {
|
|
36
|
+
data.internalRegistry = registry;
|
|
37
|
+
} else {
|
|
38
|
+
data.warnings.push(
|
|
39
|
+
"No docs/internal/doc-registry.yaml found — run `wwa doc-review` to generate it"
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (data.errors.length > 0 && data.sessions.length === 0) {
|
|
45
|
+
const error = new Error(
|
|
46
|
+
"Cannot generate case study: " + data.errors.map((e) => `- ${e}`).join(" ")
|
|
47
|
+
);
|
|
48
|
+
error.details = { errors: data.errors };
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// JSON-only path (no writes unless output is specified)
|
|
53
|
+
if (options.json) {
|
|
54
|
+
const resultJson = JSON.stringify(data, null, 2);
|
|
55
|
+
if (options.output) {
|
|
56
|
+
safeWriteFile(options.output, resultJson);
|
|
57
|
+
return {
|
|
58
|
+
mode: "json_to_file",
|
|
59
|
+
output: options.output,
|
|
60
|
+
sessions: data.sessions.length,
|
|
61
|
+
warnings: data.warnings,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
mode: "json_inline",
|
|
66
|
+
data,
|
|
67
|
+
warnings: data.warnings,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const created = {
|
|
72
|
+
mode: options.yamlOnly ? "yaml_only" : "markdown_plus_yaml",
|
|
73
|
+
markdownPath: null,
|
|
74
|
+
yamlPath: null,
|
|
75
|
+
sessions: data.sessions.length,
|
|
76
|
+
warnings: data.warnings,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// YAML-only mode: write a single .yaml artifact with all structured data.
|
|
80
|
+
if (options.yamlOnly) {
|
|
81
|
+
const baseDir = join(dir, "case-studies");
|
|
82
|
+
const baseSlug = slugify(data.projectName);
|
|
83
|
+
let yamlOutputPath = options.output || join(baseDir, `${baseSlug}.yaml`);
|
|
84
|
+
|
|
85
|
+
// Avoid overwriting existing case studies when using the default path.
|
|
86
|
+
if (!options.output) {
|
|
87
|
+
let counter = 1;
|
|
88
|
+
while (existsSync(yamlOutputPath)) {
|
|
89
|
+
yamlOutputPath = join(baseDir, `${baseSlug}-${counter}.yaml`);
|
|
90
|
+
counter += 1;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const yamlDir = resolve(yamlOutputPath, "..");
|
|
95
|
+
if (!existsSync(yamlDir)) {
|
|
96
|
+
mkdirSync(yamlDir, { recursive: true });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const ok = await writeCaseStudyYaml(yamlOutputPath, data);
|
|
100
|
+
if (!ok) {
|
|
101
|
+
const error = new Error("Failed to write YAML case study.");
|
|
102
|
+
error.details = { target: yamlOutputPath };
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
created.yamlPath = yamlOutputPath;
|
|
107
|
+
return created;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Default: write markdown case study plus companion YAML.
|
|
111
|
+
const baseDir = join(dir, "case-studies");
|
|
112
|
+
const baseSlug = slugify(data.projectName);
|
|
113
|
+
let markdownPath = options.output || join(baseDir, `${baseSlug}.md`);
|
|
114
|
+
|
|
115
|
+
// Avoid overwriting existing case studies when using the default path.
|
|
116
|
+
if (!options.output) {
|
|
117
|
+
let counter = 1;
|
|
118
|
+
while (existsSync(markdownPath)) {
|
|
119
|
+
markdownPath = join(baseDir, `${baseSlug}-${counter}.md`);
|
|
120
|
+
counter += 1;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const outputDir = resolve(markdownPath, "..");
|
|
125
|
+
if (!existsSync(outputDir)) {
|
|
126
|
+
mkdirSync(outputDir, { recursive: true });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const caseStudy = generateCaseStudy(data);
|
|
130
|
+
safeWriteFile(markdownPath, caseStudy);
|
|
131
|
+
|
|
132
|
+
const yamlPath = markdownPath.replace(/\.md$/, ".yaml");
|
|
133
|
+
const yamlResult = await writeCaseStudyYaml(yamlPath, data);
|
|
134
|
+
if (!yamlResult) {
|
|
135
|
+
const error = new Error("Failed to write companion YAML case study.");
|
|
136
|
+
error.details = { markdownPath, yamlPath };
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
created.markdownPath = markdownPath;
|
|
141
|
+
created.yamlPath = yamlPath;
|
|
142
|
+
return created;
|
|
143
|
+
}
|
|
144
|
+
|
|
18
145
|
export function register(program) {
|
|
19
146
|
program
|
|
20
147
|
.command("casestudy [directory]")
|
|
@@ -30,108 +157,41 @@ export function register(program) {
|
|
|
30
157
|
)
|
|
31
158
|
.option("--internal-registry", "Include docs/internal/doc-registry.yaml data in output")
|
|
32
159
|
.action(async (directory, opts) => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (registry) {
|
|
44
|
-
data.internalRegistry = registry;
|
|
45
|
-
} else {
|
|
46
|
-
data.warnings.push(
|
|
47
|
-
"No docs/internal/doc-registry.yaml found — run `wwa doc-review` to generate it"
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (data.errors.length > 0 && data.sessions.length === 0) {
|
|
53
|
-
console.error("Cannot generate case study:");
|
|
54
|
-
for (const e of data.errors) console.error(` - ${e}`);
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (opts.json) {
|
|
59
|
-
const output = opts.output;
|
|
60
|
-
const result = JSON.stringify(data, null, 2);
|
|
61
|
-
if (output) {
|
|
62
|
-
safeWriteFile(output, result);
|
|
63
|
-
console.log(`Case study data written to ${output}`);
|
|
64
|
-
} else {
|
|
65
|
-
console.log(result);
|
|
66
|
-
}
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// YAML-only mode: write a single .yaml artifact with all structured data.
|
|
71
|
-
if (opts.yamlOnly) {
|
|
72
|
-
const baseDir = join(d, "case-studies");
|
|
73
|
-
const baseSlug = slugify(data.projectName);
|
|
74
|
-
let yamlOutputPath = opts.output || join(baseDir, `${baseSlug}.yaml`);
|
|
75
|
-
|
|
76
|
-
// Avoid overwriting existing case studies when using the default path.
|
|
77
|
-
if (!opts.output) {
|
|
78
|
-
let counter = 1;
|
|
79
|
-
while (existsSync(yamlOutputPath)) {
|
|
80
|
-
yamlOutputPath = join(baseDir, `${baseSlug}-${counter}.yaml`);
|
|
81
|
-
counter += 1;
|
|
160
|
+
try {
|
|
161
|
+
const result = await runCasestudy(directory, opts);
|
|
162
|
+
|
|
163
|
+
if (opts.json) {
|
|
164
|
+
if (result.mode === "json_inline") {
|
|
165
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
166
|
+
} else {
|
|
167
|
+
console.log(
|
|
168
|
+
`Case study data written to ${result.output} (${result.sessions} sessions)`
|
|
169
|
+
);
|
|
82
170
|
}
|
|
171
|
+
return;
|
|
83
172
|
}
|
|
84
173
|
|
|
85
|
-
|
|
86
|
-
if (!existsSync(yamlDir)) {
|
|
87
|
-
mkdirSync(yamlDir, { recursive: true });
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const ok = await writeCaseStudyYaml(yamlOutputPath, data);
|
|
91
|
-
if (ok) {
|
|
174
|
+
if (result.mode === "yaml_only") {
|
|
92
175
|
console.log(
|
|
93
|
-
`Case study YAML written to ${
|
|
176
|
+
`Case study YAML written to ${result.yamlPath} (${result.sessions} sessions)`
|
|
94
177
|
);
|
|
95
178
|
} else {
|
|
96
|
-
console.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// Default: write markdown case study plus companion YAML.
|
|
101
|
-
const baseDir = join(d, "case-studies");
|
|
102
|
-
const baseSlug = slugify(data.projectName);
|
|
103
|
-
let markdownPath = opts.output || join(baseDir, `${baseSlug}.md`);
|
|
104
|
-
|
|
105
|
-
// Avoid overwriting existing case studies when using the default path.
|
|
106
|
-
if (!opts.output) {
|
|
107
|
-
let counter = 1;
|
|
108
|
-
while (existsSync(markdownPath)) {
|
|
109
|
-
markdownPath = join(baseDir, `${baseSlug}-${counter}.md`);
|
|
110
|
-
counter += 1;
|
|
111
|
-
}
|
|
179
|
+
console.log(
|
|
180
|
+
`Case study written to ${result.markdownPath} (${result.sessions} sessions)`
|
|
181
|
+
);
|
|
182
|
+
console.log(`Case study YAML written to ${result.yamlPath}`);
|
|
112
183
|
}
|
|
113
184
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
185
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
186
|
+
console.log("\nWarnings:");
|
|
187
|
+
for (const w of result.warnings) console.log(` - ${w}`);
|
|
117
188
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
`Case study written to ${markdownPath} (${data.sessions.length} sessions)`
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
const yamlPath = markdownPath.replace(/\.md$/, ".yaml");
|
|
126
|
-
const yamlResult = await writeCaseStudyYaml(yamlPath, data);
|
|
127
|
-
if (yamlResult) {
|
|
128
|
-
console.log(`Case study YAML written to ${yamlPath}`);
|
|
189
|
+
} catch (e) {
|
|
190
|
+
console.error(e.message || "Failed to generate case study.");
|
|
191
|
+
if (e.details?.errors) {
|
|
192
|
+
for (const err of e.details.errors) console.error(` - ${err}`);
|
|
129
193
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (data.warnings.length > 0) {
|
|
133
|
-
console.log("\nWarnings:");
|
|
134
|
-
for (const w of data.warnings) console.log(` - ${w}`);
|
|
194
|
+
process.exit(1);
|
|
135
195
|
}
|
|
136
196
|
});
|
|
137
197
|
}
|