agent-method 1.5.12
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/README.md +343 -0
- package/bin/wwa.js +115 -0
- package/docs/internal/cli-commands.yaml +259 -0
- package/docs/internal/doc-tokens.yaml +1103 -0
- package/docs/internal/feature-registry.yaml +1643 -0
- package/lib/boundaries.js +247 -0
- package/lib/cli/add.js +170 -0
- package/lib/cli/casestudy.js +1000 -0
- package/lib/cli/check.js +323 -0
- package/lib/cli/close.js +838 -0
- package/lib/cli/completion.js +735 -0
- package/lib/cli/deps.js +234 -0
- package/lib/cli/digest.js +73 -0
- package/lib/cli/doc-review.js +486 -0
- package/lib/cli/docs.js +315 -0
- package/lib/cli/helpers.js +198 -0
- package/lib/cli/implement.js +169 -0
- package/lib/cli/init.js +280 -0
- package/lib/cli/pipeline.js +206 -0
- package/lib/cli/plan.js +140 -0
- package/lib/cli/record.js +98 -0
- package/lib/cli/refine.js +202 -0
- package/lib/cli/report-helpers.js +113 -0
- package/lib/cli/review.js +76 -0
- package/lib/cli/routable.js +109 -0
- package/lib/cli/route.js +101 -0
- package/lib/cli/scan.js +133 -0
- package/lib/cli/serve.js +23 -0
- package/lib/cli/status.js +65 -0
- package/lib/cli/update-docs.js +574 -0
- package/lib/cli/upgrade.js +222 -0
- package/lib/cli/watch.js +32 -0
- package/lib/dependencies.js +196 -0
- package/lib/init.js +692 -0
- package/lib/mcp-server.js +612 -0
- package/lib/pipeline.js +907 -0
- package/lib/registry.js +132 -0
- package/lib/watcher.js +165 -0
- package/package.json +54 -0
- package/templates/README.md +363 -0
- package/templates/entry-points/.cursorrules +90 -0
- package/templates/entry-points/AGENT.md +90 -0
- package/templates/entry-points/CLAUDE.md +88 -0
- package/templates/extensions/MANIFEST.md +110 -0
- package/templates/extensions/analytical-system.md +96 -0
- package/templates/extensions/code-project.md +77 -0
- package/templates/extensions/data-exploration.md +117 -0
- package/templates/full/.context/BASE.md +101 -0
- package/templates/full/.context/COMPOSITION.md +47 -0
- package/templates/full/.context/INDEX.yaml +56 -0
- package/templates/full/.context/METHODOLOGY.md +246 -0
- package/templates/full/.context/PROTOCOL.yaml +169 -0
- package/templates/full/.context/REGISTRY.md +75 -0
- package/templates/full/.cursorrules +90 -0
- package/templates/full/AGENT.md +90 -0
- package/templates/full/CLAUDE.md +90 -0
- package/templates/full/Management/DIGEST.md +23 -0
- package/templates/full/Management/STATUS.md +46 -0
- package/templates/full/PLAN.md +67 -0
- package/templates/full/PROJECT-PROFILE.md +61 -0
- package/templates/full/PROJECT.md +80 -0
- package/templates/full/REQUIREMENTS.md +30 -0
- package/templates/full/ROADMAP.md +39 -0
- package/templates/full/Reviews/INDEX.md +41 -0
- package/templates/full/Reviews/backlog.md +52 -0
- package/templates/full/Reviews/plan.md +43 -0
- package/templates/full/Reviews/project.md +41 -0
- package/templates/full/Reviews/requirements.md +42 -0
- package/templates/full/Reviews/roadmap.md +41 -0
- package/templates/full/Reviews/state.md +56 -0
- package/templates/full/SESSION-LOG.md +102 -0
- package/templates/full/STATE.md +42 -0
- package/templates/full/SUMMARY.md +27 -0
- package/templates/full/agentWorkflows/INDEX.md +42 -0
- package/templates/full/agentWorkflows/observations.md +65 -0
- package/templates/full/agentWorkflows/patterns.md +68 -0
- package/templates/full/agentWorkflows/sessions.md +92 -0
- package/templates/full/intro/README.md +39 -0
- package/templates/full/registry/feature-registry.yaml +25 -0
- package/templates/full/registry/features/catalog.yaml +743 -0
- package/templates/full/registry/features/protocol.yaml +121 -0
- package/templates/full/registry/features/routing.yaml +358 -0
- package/templates/full/registry/features/workflows.yaml +404 -0
- package/templates/full/todos/backlog.md +19 -0
- package/templates/starter/.context/BASE.md +66 -0
- package/templates/starter/.context/INDEX.yaml +51 -0
- package/templates/starter/.context/METHODOLOGY.md +228 -0
- package/templates/starter/.context/PROTOCOL.yaml +165 -0
- package/templates/starter/.cursorrules +90 -0
- package/templates/starter/AGENT.md +90 -0
- package/templates/starter/CLAUDE.md +90 -0
- package/templates/starter/Management/DIGEST.md +23 -0
- package/templates/starter/Management/STATUS.md +46 -0
- package/templates/starter/PLAN.md +67 -0
- package/templates/starter/PROJECT-PROFILE.md +44 -0
- package/templates/starter/PROJECT.md +80 -0
- package/templates/starter/ROADMAP.md +39 -0
- package/templates/starter/Reviews/INDEX.md +75 -0
- package/templates/starter/SESSION-LOG.md +102 -0
- package/templates/starter/STATE.md +42 -0
- package/templates/starter/SUMMARY.md +27 -0
- package/templates/starter/agentWorkflows/INDEX.md +61 -0
- package/templates/starter/intro/README.md +37 -0
- package/templates/starter/registry/feature-registry.yaml +25 -0
- package/templates/starter/registry/features/catalog.yaml +743 -0
- package/templates/starter/registry/features/protocol.yaml +121 -0
- package/templates/starter/registry/features/routing.yaml +358 -0
- package/templates/starter/registry/features/workflows.yaml +404 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configurable project boundaries — path resolution, visibility, distribution.
|
|
3
|
+
* Reads `boundaries` section from PROTOCOL.yaml. Falls back to DEFAULTS
|
|
4
|
+
* when no boundaries section exists (backward compatible).
|
|
5
|
+
*
|
|
6
|
+
* Used by: every CLI command that reads/writes project registries.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
10
|
+
import { resolve, join, dirname } from "node:path";
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Defaults — match current hardcoded paths exactly
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
const DEFAULTS = {
|
|
17
|
+
registries: {
|
|
18
|
+
tokens: "registry/doc-tokens.yaml",
|
|
19
|
+
docs_map: ".context/DOCS-MAP.md",
|
|
20
|
+
index: ".context/INDEX.yaml",
|
|
21
|
+
feature_registry: "registry/feature-registry.yaml",
|
|
22
|
+
output: "docs/internal/",
|
|
23
|
+
},
|
|
24
|
+
visibility: {
|
|
25
|
+
internal: [".context/", "registry/", "research/", "todos/", "docs/internal/"],
|
|
26
|
+
release: [
|
|
27
|
+
"docs/architecture/",
|
|
28
|
+
"docs/workflow/",
|
|
29
|
+
"docs/guides/",
|
|
30
|
+
"templates/",
|
|
31
|
+
"case-studies/",
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
scan: {
|
|
35
|
+
include: ["docs/", ".context/", "templates/", "registry/"],
|
|
36
|
+
exclude: ["node_modules/", ".git/", "dist/", "build/"],
|
|
37
|
+
},
|
|
38
|
+
distribution: {
|
|
39
|
+
git: {
|
|
40
|
+
exclude: [
|
|
41
|
+
"STATE.md", "PLAN.md", "ROADMAP.md", "SUMMARY.md",
|
|
42
|
+
".context/", "research/", "todos/",
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
npm: {
|
|
46
|
+
include: ["bin/", "lib/", "templates/"],
|
|
47
|
+
exclude: ["docs/internal/", ".context/", "research/"],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Cache — one parse per directory per CLI invocation
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
const _cache = new Map();
|
|
57
|
+
|
|
58
|
+
/** Deep merge source into target (source wins, arrays replaced not concatenated). */
|
|
59
|
+
function deepMerge(target, source) {
|
|
60
|
+
const result = { ...target };
|
|
61
|
+
for (const key of Object.keys(source)) {
|
|
62
|
+
if (
|
|
63
|
+
source[key] &&
|
|
64
|
+
typeof source[key] === "object" &&
|
|
65
|
+
!Array.isArray(source[key]) &&
|
|
66
|
+
target[key] &&
|
|
67
|
+
typeof target[key] === "object" &&
|
|
68
|
+
!Array.isArray(target[key])
|
|
69
|
+
) {
|
|
70
|
+
result[key] = deepMerge(target[key], source[key]);
|
|
71
|
+
} else {
|
|
72
|
+
result[key] = source[key];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Core loader
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Load boundaries from PROTOCOL.yaml, deep-merged with DEFAULTS.
|
|
84
|
+
* PROTOCOL.yaml path is hardcoded — it's the anchor.
|
|
85
|
+
*/
|
|
86
|
+
export async function loadBoundaries(dir) {
|
|
87
|
+
const d = resolve(dir);
|
|
88
|
+
if (_cache.has(d)) return _cache.get(d);
|
|
89
|
+
|
|
90
|
+
const protocolPath = join(d, ".context", "PROTOCOL.yaml");
|
|
91
|
+
let boundaries = { ...DEFAULTS };
|
|
92
|
+
|
|
93
|
+
if (existsSync(protocolPath)) {
|
|
94
|
+
try {
|
|
95
|
+
const yaml = (await import("js-yaml")).default;
|
|
96
|
+
const raw = readFileSync(protocolPath, "utf-8");
|
|
97
|
+
const parsed = yaml.load(raw);
|
|
98
|
+
if (parsed?.boundaries) {
|
|
99
|
+
boundaries = deepMerge(DEFAULTS, parsed.boundaries);
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
// Parse error — use defaults
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Legacy fallback: if resolved registry paths don't exist, check .context/
|
|
107
|
+
for (const key of ["tokens", "feature_registry"]) {
|
|
108
|
+
const resolved = join(d, boundaries.registries[key]);
|
|
109
|
+
if (!existsSync(resolved)) {
|
|
110
|
+
const legacyName = key === "tokens" ? "doc-tokens.yaml" : "feature-registry.yaml";
|
|
111
|
+
const legacy = join(d, ".context", legacyName);
|
|
112
|
+
if (existsSync(legacy)) {
|
|
113
|
+
boundaries.registries[key] = `.context/${legacyName}`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
_cache.set(d, boundaries);
|
|
119
|
+
return boundaries;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// Path resolvers
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
/** Resolve path to the token registry (doc-tokens.yaml). */
|
|
127
|
+
export async function resolveTokensPath(dir) {
|
|
128
|
+
const b = await loadBoundaries(dir);
|
|
129
|
+
return join(resolve(dir), b.registries.tokens);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Resolve path to the docs map (DOCS-MAP.md). */
|
|
133
|
+
export async function resolveDocsMapPath(dir) {
|
|
134
|
+
const b = await loadBoundaries(dir);
|
|
135
|
+
return join(resolve(dir), b.registries.docs_map);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Resolve path to the project index (INDEX.yaml). */
|
|
139
|
+
export async function resolveIndexPath(dir) {
|
|
140
|
+
const b = await loadBoundaries(dir);
|
|
141
|
+
return join(resolve(dir), b.registries.index);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Resolve path to the feature registry. */
|
|
145
|
+
export async function resolveFeatureRegistryPath(dir) {
|
|
146
|
+
const b = await loadBoundaries(dir);
|
|
147
|
+
return join(resolve(dir), b.registries.feature_registry);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Resolve path to the output directory (docs/internal/ by default). */
|
|
151
|
+
export async function resolveOutputDir(dir) {
|
|
152
|
+
const b = await loadBoundaries(dir);
|
|
153
|
+
return join(resolve(dir), b.registries.output);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
// Centralized doc graph loader (replaces 3 duplicate functions)
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
/** Load doc_graph from the project's token registry (handles split layout). */
|
|
161
|
+
export async function loadDocGraph(dir) {
|
|
162
|
+
const tokensPath = await resolveTokensPath(dir);
|
|
163
|
+
if (!existsSync(tokensPath)) return null;
|
|
164
|
+
try {
|
|
165
|
+
const yaml = (await import("js-yaml")).default;
|
|
166
|
+
const raw = readFileSync(tokensPath, "utf-8");
|
|
167
|
+
const parsed = yaml.load(raw);
|
|
168
|
+
|
|
169
|
+
// Check for externalized doc_graph component
|
|
170
|
+
if (parsed?.components?.doc_graph) {
|
|
171
|
+
const componentPath = join(dirname(tokensPath), parsed.components.doc_graph);
|
|
172
|
+
if (existsSync(componentPath)) {
|
|
173
|
+
const cParsed = yaml.load(readFileSync(componentPath, "utf-8"));
|
|
174
|
+
return cParsed?.doc_graph || cParsed || null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return parsed?.doc_graph || null;
|
|
179
|
+
} catch {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Load the full token registry, assembling externalized sections.
|
|
186
|
+
* Returns the complete parsed object with all sections merged.
|
|
187
|
+
*/
|
|
188
|
+
export async function loadTokensAssembled(dir) {
|
|
189
|
+
const tokensPath = await resolveTokensPath(dir);
|
|
190
|
+
if (!existsSync(tokensPath)) return null;
|
|
191
|
+
try {
|
|
192
|
+
const yaml = (await import("js-yaml")).default;
|
|
193
|
+
const raw = readFileSync(tokensPath, "utf-8");
|
|
194
|
+
const parsed = yaml.load(raw);
|
|
195
|
+
if (!parsed) return null;
|
|
196
|
+
|
|
197
|
+
// Assemble externalized sections
|
|
198
|
+
if (parsed.components) {
|
|
199
|
+
const tokensDir = dirname(tokensPath);
|
|
200
|
+
for (const [section, relPath] of Object.entries(parsed.components)) {
|
|
201
|
+
const componentPath = join(tokensDir, relPath);
|
|
202
|
+
if (existsSync(componentPath)) {
|
|
203
|
+
const cParsed = yaml.load(readFileSync(componentPath, "utf-8"));
|
|
204
|
+
parsed[section] = cParsed?.[section] || cParsed;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
delete parsed.components;
|
|
208
|
+
}
|
|
209
|
+
return parsed;
|
|
210
|
+
} catch {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// Visibility helpers
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
/** Check if a file path falls within internal (dev-only) boundaries. */
|
|
220
|
+
export function isInternal(filePath, boundaries) {
|
|
221
|
+
const p = filePath.replace(/\\/g, "/");
|
|
222
|
+
return (boundaries.visibility?.internal || []).some((prefix) =>
|
|
223
|
+
p.startsWith(prefix)
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Check if a file path falls within release boundaries. */
|
|
228
|
+
export function isRelease(filePath, boundaries) {
|
|
229
|
+
const p = filePath.replace(/\\/g, "/");
|
|
230
|
+
return (boundaries.visibility?.release || []).some((prefix) =>
|
|
231
|
+
p.startsWith(prefix)
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
// Utilities
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
/** Clear the boundaries cache (for testing). */
|
|
240
|
+
export function clearCache() {
|
|
241
|
+
_cache.clear();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Get the raw DEFAULTS object (for init seeding). */
|
|
245
|
+
export function getDefaults() {
|
|
246
|
+
return DEFAULTS;
|
|
247
|
+
}
|
package/lib/cli/add.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wwa add — append structured content to methodology files (Phase 7p).
|
|
3
|
+
* Targets: backlog, decision, finding, session, summary.
|
|
4
|
+
* Uses safeWriteFile; only .md/.yaml/.yml. Optional --dry-run.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, existsSync, readdirSync } from "node:fs";
|
|
8
|
+
import { resolve, join } from "node:path";
|
|
9
|
+
import { findSessionLog } from "./helpers.js";
|
|
10
|
+
import { safeWriteFile, isSafeToWrite } from "./helpers.js";
|
|
11
|
+
|
|
12
|
+
const ADD_TYPES = ["backlog", "decision", "finding", "session", "summary"];
|
|
13
|
+
|
|
14
|
+
function today() {
|
|
15
|
+
return new Date().toISOString().slice(0, 10);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function readStdin() {
|
|
19
|
+
return new Promise((resolveStdin) => {
|
|
20
|
+
const chunks = [];
|
|
21
|
+
process.stdin.setEncoding("utf8");
|
|
22
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
23
|
+
process.stdin.on("end", () => resolveStdin(chunks.join("").trim()));
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function register(program) {
|
|
28
|
+
program
|
|
29
|
+
.command("add <type> [content...]")
|
|
30
|
+
.description("Append content to methodology files (backlog, decision, finding, session, summary)")
|
|
31
|
+
.option("-d, --directory <path>", "Project directory", ".")
|
|
32
|
+
.option("--file <path>", "Read content from file (for session/summary)")
|
|
33
|
+
.option("--dry-run", "Show what would be written without writing")
|
|
34
|
+
.option("--json", "Output target path and content as JSON")
|
|
35
|
+
.action(async (type, contentParts, opts) => {
|
|
36
|
+
const dir = resolve(opts.directory);
|
|
37
|
+
const dryRun = opts.dryRun || false;
|
|
38
|
+
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
|
+
let content;
|
|
46
|
+
if (opts.file) {
|
|
47
|
+
const fpath = resolve(opts.file);
|
|
48
|
+
if (!existsSync(fpath)) {
|
|
49
|
+
console.error(`File not found: ${fpath}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
content = readFileSync(fpath, "utf-8").trim();
|
|
53
|
+
} else if (contentParts && contentParts.length > 0) {
|
|
54
|
+
content = contentParts.join(" ").trim();
|
|
55
|
+
} else if (type === "session" || type === "summary") {
|
|
56
|
+
content = await readStdin();
|
|
57
|
+
if (!content) {
|
|
58
|
+
console.error("Provide content via stdin or --file for session/summary.");
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
console.error(`Provide content as argument or --file for type: ${type}`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let targetPath;
|
|
67
|
+
let toAppend;
|
|
68
|
+
|
|
69
|
+
switch (type) {
|
|
70
|
+
case "backlog": {
|
|
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
|
+
}
|
|
128
|
+
|
|
129
|
+
if (dryRun || asJson) {
|
|
130
|
+
if (asJson) {
|
|
131
|
+
console.log(JSON.stringify({ target: targetPath, content: toAppend, dryRun }, null, 2));
|
|
132
|
+
} else {
|
|
133
|
+
console.log("Target:", targetPath);
|
|
134
|
+
console.log("Content to append:");
|
|
135
|
+
console.log(toAppend);
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const dirToEnsure = resolve(targetPath, "..");
|
|
141
|
+
if (!existsSync(dirToEnsure)) {
|
|
142
|
+
const { mkdirSync } = await import("node:fs");
|
|
143
|
+
mkdirSync(dirToEnsure, { recursive: true });
|
|
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;
|
|
152
|
+
}
|
|
153
|
+
console.error(`File not found: ${targetPath}. Create it first or run from project root.`);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const existing = readFileSync(targetPath, "utf-8");
|
|
158
|
+
safeWriteFile(targetPath, existing + toAppend);
|
|
159
|
+
console.log("Appended to", targetPath);
|
|
160
|
+
remindCascade(dir);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Remind to check PROTOCOL cascade after file change (respect PROTOCOL). */
|
|
165
|
+
function remindCascade(dir) {
|
|
166
|
+
const p = join(dir, ".context", "PROTOCOL.yaml");
|
|
167
|
+
if (existsSync(p)) {
|
|
168
|
+
console.log("Reminder: after file changes, check PROTOCOL cascade (.context/PROTOCOL.yaml).");
|
|
169
|
+
}
|
|
170
|
+
}
|