@tea-agent/loop-agent 0.35.1-beta.0 → 0.35.1-beta.2

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 (179) hide show
  1. package/AGENTS.md +110 -108
  2. package/CHANGELOG.md +24 -26
  3. package/README.md +165 -165
  4. package/bin/agent-worker.js +0 -0
  5. package/bin/loop-agent.js +57 -21
  6. package/dist/application/task-lifecycle/advance.js +0 -1
  7. package/dist/build-stamp.json +6 -0
  8. package/dist/cli/program.js +2 -2
  9. package/dist/commands/cursor-prompt.js +6 -6
  10. package/dist/commands/init-upgrade.js +19 -351
  11. package/dist/commands/init.js +67 -14
  12. package/dist/commands/loop-benchmark.js +11 -11
  13. package/dist/commands/pi-reuse-benchmark.js +16 -16
  14. package/dist/commands/run-dag-progress.js +0 -14
  15. package/dist/commands/task-advance.js +3 -33
  16. package/dist/executors/dag-pi-executor.js +44 -0
  17. package/dist/shared/operator/capabilities.js +1 -38
  18. package/dist/shared/package-metadata.js +42 -0
  19. package/dist/sidecars/cursor-prompt/executor.js +1 -1
  20. package/dist/worker/console/chat/pi-runtime.js +25 -41
  21. package/dist/worker/console/chat/routes.js +4 -27
  22. package/dist/worker/console/operation-runner.js +0 -24
  23. package/dist/worker/console/operator-actions.js +0 -58
  24. package/dist/worker/console/static/assets/index-CvsQgALl.js +56 -0
  25. package/dist/worker/console/static/assets/{index-Dups4sSM.css → index-hJqCPs_g.css} +1 -1
  26. package/dist/worker/console/static/index.html +2 -2
  27. package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -0
  28. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -13
  29. package/dist/worker/console/static-src/operator-chat/useComposer.js +7 -30
  30. package/dist/worker/loop-agent/loop-agent-client.js +17 -3
  31. package/dist/worker/observability/read-model.js +20 -0
  32. package/dist/worker/observe/static/copy.js +67 -67
  33. package/dist/worker/observe/static/dag-layout.d.ts +36 -36
  34. package/dist/worker/observe/static/dom.js +220 -220
  35. package/dist/worker/observe/static/relations.js +133 -133
  36. package/dist/worker/observe/static/run-processing.js +148 -148
  37. package/dist/worker/observe/static/views/batch.js +227 -227
  38. package/dist/worker/observe/static/views/failures.js +143 -143
  39. package/dist/worker/observe/static/views/feature.js +492 -492
  40. package/dist/worker/observe/static/views/run.js +453 -453
  41. package/dist/worker/observe/static/views/shell.js +7 -7
  42. package/dist/worker/observe/static/views/timeline.js +163 -163
  43. package/dist/worker/preflight.js +2 -1
  44. package/dist/workflows/dag/backend-test-scenario-param.js +33 -23
  45. package/dist/workflows/dag/canvas-observer.js +275 -275
  46. package/dist/workflows/dag/contract-output-registry.js +14 -0
  47. package/dist/workflows/dag/contract-validator-registrations.js +8 -0
  48. package/dist/workflows/dag/dynamic-runtime/shared.js +9 -1
  49. package/dist/workflows/dag/frontend-implementation-contract.js +233 -39
  50. package/dist/workflows/dag/frontend-prewrite-gate.js +364 -61
  51. package/dist/workflows/dag/frontend-recovery-plan.js +73 -0
  52. package/dist/workflows/dag/frontend-recovery-root-manifest.js +123 -0
  53. package/dist/workflows/dag/frontend-recovery-run.js +539 -0
  54. package/dist/workflows/dag/frontend-repair.js +219 -18
  55. package/dist/workflows/dag/frontend-verification-trace.js +47 -32
  56. package/dist/workflows/dag/frontend-writer-recovery.js +106 -0
  57. package/dist/workflows/dag/frontend-writer-rollback.js +821 -0
  58. package/dist/workflows/dag/init-hybrid.js +41 -24
  59. package/dist/workflows/dag/node-execution.js +89 -0
  60. package/dist/workflows/dag/recovery-recommendation.js +58 -0
  61. package/dist/workflows/dag/runner.js +245 -11
  62. package/dist/workflows/dag/scheduler.js +257 -3
  63. package/dist/workflows/dag/types.js +130 -2
  64. package/docs/architecture/evolution.md +73 -73
  65. package/docs/architecture/system-overview.md +100 -100
  66. package/docs/architecture/worker-and-feature.md +122 -122
  67. package/docs/skills/README.md +7 -7
  68. package/docs/templates/adr.md +60 -60
  69. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  70. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  71. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  72. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  73. package/docs/templates/agent-dag-report.schema.json +473 -473
  74. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  75. package/docs/templates/backend-test-result.schema.json +99 -99
  76. package/docs/templates/evaluation/agents-map-slim-v1.md +87 -87
  77. package/docs/templates/evaluation/agents-map-verbose-v0.md +153 -153
  78. package/docs/templates/feature-spec.md +53 -53
  79. package/docs/templates/frontend-design-contract.md +42 -42
  80. package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -17
  81. package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -16
  82. package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -16
  83. package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -16
  84. package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -16
  85. package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -16
  86. package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -21
  87. package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -29
  88. package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -28
  89. package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -28
  90. package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -29
  91. package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -27
  92. package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -28
  93. package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -28
  94. package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -27
  95. package/docs/templates/frontend-eval/metrics.md +138 -138
  96. package/docs/templates/frontend-eval/smoke-targets.md +53 -53
  97. package/docs/templates/frontend-task-constraints.md +35 -35
  98. package/docs/templates/frontend-task-requirement.md +70 -70
  99. package/docs/templates/init-evolution-review.md +35 -35
  100. package/docs/templates/init-managed-agents.md +154 -156
  101. package/docs/templates/interactive-ui-round2-experiment.md +66 -66
  102. package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -118
  103. package/docs/templates/knowledge-sync-dag.json +178 -178
  104. package/docs/templates/knowledge-sync-draft.schema.json +71 -71
  105. package/docs/templates/product-line/closeout.yaml +9 -9
  106. package/docs/templates/product-line/design.md +13 -13
  107. package/docs/templates/product-line/links.md +10 -10
  108. package/docs/templates/product-line/requirement.md +17 -17
  109. package/docs/templates/product-line/test-plan.md +7 -7
  110. package/docs/templates/project-start-checklist.md +9 -9
  111. package/docs/templates/qa-report.md +48 -48
  112. package/docs/templates/sprint-contract.md +29 -29
  113. package/docs/templates/worker-dogfood-evidence.md +80 -80
  114. package/docs/templates/worker-dogfood-setup.md +68 -68
  115. package/harness.json +2 -5
  116. package/package.json +2 -2
  117. package/scripts/kb-bootstrap-init-skeleton.sh +0 -0
  118. package/scripts/kb-graph-incremental-prepare.mjs +0 -0
  119. package/scripts/kb-graph-materialize.mjs +105 -105
  120. package/scripts/kb-graph-promote.mjs +164 -164
  121. package/scripts/kb-query.mjs +554 -554
  122. package/skills/agent-worker/SKILL.md +48 -48
  123. package/skills/agent-worker/references/agent-worker-operator.md +159 -159
  124. package/skills/ai-engineering-context/SKILL.md +48 -48
  125. package/skills/analyze-product-dependencies/scripts/test-validators.mjs +0 -0
  126. package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +0 -0
  127. package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +0 -0
  128. package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +0 -0
  129. package/skills/analyze-product-requirements/scripts/compute-source-identity.mjs +0 -0
  130. package/skills/analyze-product-requirements/scripts/test-validators.mjs +0 -0
  131. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +0 -0
  132. package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +0 -0
  133. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +0 -0
  134. package/skills/browser-tools/browser-content.js +103 -103
  135. package/skills/browser-tools/browser-cookies.js +35 -35
  136. package/skills/browser-tools/browser-eval.js +53 -53
  137. package/skills/browser-tools/browser-hn-scraper.js +108 -108
  138. package/skills/browser-tools/browser-nav.js +44 -44
  139. package/skills/browser-tools/browser-pick.js +162 -162
  140. package/skills/browser-tools/browser-screenshot.js +34 -34
  141. package/skills/browser-tools/browser-start.js +86 -86
  142. package/skills/browser-tools/package-lock.json +2556 -2556
  143. package/skills/browser-tools/package.json +19 -19
  144. package/skills/code-review-core/SKILL.md +20 -20
  145. package/skills/codebase-scout/SKILL.md +19 -19
  146. package/skills/grill-me/SKILL.md +10 -10
  147. package/skills/local-jacoco-coverage/scripts/run-coverage-analysis.sh +0 -0
  148. package/skills/local-jacoco-coverage/scripts/start-jacoco-agent.sh +0 -0
  149. package/skills/loop-agent/SKILL.md +0 -1
  150. package/skills/loop-agent/references/command-reference.md +639 -641
  151. package/skills/loop-agent/references/docs-converge.md +126 -126
  152. package/skills/loop-agent/references/learned/README.md +21 -21
  153. package/skills/loop-agent/references/pi-prompt.md +23 -23
  154. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
  155. package/skills/playwright-cli/references/element-attributes.md +23 -23
  156. package/skills/playwright-cli/references/playwright-tests.md +39 -39
  157. package/skills/playwright-cli/references/request-mocking.md +87 -87
  158. package/skills/playwright-cli/references/running-code.md +241 -241
  159. package/skills/playwright-cli/references/session-management.md +225 -225
  160. package/skills/playwright-cli/references/storage-state.md +275 -275
  161. package/skills/playwright-cli/references/test-generation.md +433 -433
  162. package/skills/requesting-code-review/SKILL.md +101 -101
  163. package/skills/requesting-code-review/code-reviewer.md +168 -168
  164. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  165. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  166. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  167. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  168. package/skills/systematic-debugging/find-polluter.sh +63 -63
  169. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  170. package/skills/systematic-debugging/test-academic.md +14 -14
  171. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  172. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  173. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  174. package/skills/using-git-worktrees/SKILL.md +215 -215
  175. package/skills/verification-before-completion/SKILL.md +154 -154
  176. package/skills/webapp-testing/SKILL.md +19 -19
  177. package/dist/worker/console/operation-wait.js +0 -241
  178. package/dist/worker/console/static/assets/index-SjjjZnV3.js +0 -56
  179. package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +0 -141
@@ -1,164 +1,164 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Promote knowledge/bootstrap/staging → formal trees (merge-new-only).
4
- * Never overwrites existing formal files. Does not call an LLM.
5
- */
6
- import fs from "node:fs";
7
- import path from "node:path";
8
- import process from "node:process";
9
-
10
- function parseArgs(argv) {
11
- const out = {
12
- root: process.cwd(),
13
- from: "knowledge/bootstrap/staging",
14
- dryRun: false,
15
- json: true,
16
- };
17
- for (let i = 0; i < argv.length; i++) {
18
- const a = argv[i];
19
- if (a === "--root") out.root = path.resolve(argv[++i]);
20
- else if (a === "--from") out.from = argv[++i];
21
- else if (a === "--dry-run") out.dryRun = true;
22
- else if (a === "--json") out.json = true;
23
- else if (a === "--markdown") out.json = false;
24
- else if (a === "-h" || a === "--help") {
25
- console.log(
26
- "Usage: node scripts/kb-graph-promote.mjs [--root <dir>] [--from <staging-rel>] [--dry-run] [--json|--markdown]",
27
- );
28
- process.exit(0);
29
- } else {
30
- throw new Error(`unknown argument: ${a}`);
31
- }
32
- }
33
- return out;
34
- }
35
-
36
- function copyDirMergeNew(src, dest, dryRun, stats) {
37
- if (!fs.existsSync(src)) return;
38
- fs.mkdirSync(dest, { recursive: true });
39
- for (const ent of fs.readdirSync(src, { withFileTypes: true })) {
40
- const s = path.join(src, ent.name);
41
- const d = path.join(dest, ent.name);
42
- if (ent.isDirectory()) {
43
- copyDirMergeNew(s, d, dryRun, stats);
44
- continue;
45
- }
46
- if (fs.existsSync(d)) {
47
- stats.skipped.push(path.relative(stats.root, d).split(path.sep).join("/"));
48
- continue;
49
- }
50
- stats.copied.push(path.relative(stats.root, d).split(path.sep).join("/"));
51
- if (!dryRun) {
52
- fs.mkdirSync(path.dirname(d), { recursive: true });
53
- fs.copyFileSync(s, d);
54
- }
55
- }
56
- }
57
-
58
- function featureIdFromProposalFile(fileName) {
59
- if (!/^F-[A-Za-z0-9][A-Za-z0-9._-]*\.ya?ml$/.test(fileName)) {
60
- throw new Error(`invalid feature link proposal filename: ${fileName}`);
61
- }
62
- const id = fileName.replace(/\.ya?ml$/, "");
63
- if (id.includes("..")) {
64
- throw new Error(`invalid feature link proposal filename: ${fileName}`);
65
- }
66
- return id;
67
- }
68
-
69
- function main() {
70
- let args;
71
- try {
72
- args = parseArgs(process.argv.slice(2));
73
- } catch (e) {
74
- console.error(String(e?.message || e));
75
- process.exit(2);
76
- }
77
- const root = args.root;
78
- const staging = path.join(root, args.from);
79
- if (!fs.existsSync(staging)) {
80
- const err = {
81
- ok: false,
82
- error: `missing staging at ${args.from}`,
83
- };
84
- console.log(JSON.stringify(err, null, 2));
85
- process.exit(1);
86
- }
87
-
88
- const stats = { root, copied: [], skipped: [] };
89
- copyDirMergeNew(
90
- path.join(staging, "domains"),
91
- path.join(root, "knowledge/domains"),
92
- args.dryRun,
93
- stats,
94
- );
95
- copyDirMergeNew(
96
- path.join(staging, "services"),
97
- path.join(root, "knowledge/services"),
98
- args.dryRun,
99
- stats,
100
- );
101
- copyDirMergeNew(
102
- path.join(staging, "modules"),
103
- path.join(root, "knowledge/modules"),
104
- args.dryRun,
105
- stats,
106
- );
107
-
108
- const edgesSrc = path.join(staging, "edges.proposed.yaml");
109
- const edgesManual = path.join(root, "knowledge/graph/edges.manual.yaml");
110
- if (fs.existsSync(edgesSrc)) {
111
- if (fs.existsSync(edgesManual)) {
112
- stats.skipped.push("knowledge/graph/edges.manual.yaml");
113
- } else {
114
- stats.copied.push("knowledge/graph/edges.manual.yaml");
115
- if (!args.dryRun) {
116
- fs.mkdirSync(path.dirname(edgesManual), { recursive: true });
117
- fs.copyFileSync(edgesSrc, edgesManual);
118
- }
119
- }
120
- }
121
-
122
- const linksDir = path.join(staging, "features-links.proposed");
123
- if (fs.existsSync(linksDir)) {
124
- for (const f of fs.readdirSync(linksDir)) {
125
- if (!f.endsWith(".yaml") && !f.endsWith(".yml")) continue;
126
- const id = featureIdFromProposalFile(f);
127
- const featureDir = path.join(root, "features", id);
128
- if (!fs.existsSync(featureDir)) continue;
129
- const dest = path.join(featureDir, "knowledge-links.yaml");
130
- const rel = `features/${id}/knowledge-links.yaml`;
131
- if (fs.existsSync(dest)) {
132
- stats.skipped.push(rel);
133
- } else {
134
- stats.copied.push(rel);
135
- if (!args.dryRun) {
136
- fs.mkdirSync(path.dirname(dest), { recursive: true });
137
- fs.copyFileSync(path.join(linksDir, f), dest);
138
- }
139
- }
140
- }
141
- }
142
-
143
- const result = {
144
- ok: true,
145
- dry_run: args.dryRun,
146
- copied_count: stats.copied.length,
147
- skipped_count: stats.skipped.length,
148
- copied: stats.copied,
149
- skipped_existing: stats.skipped,
150
- policy: "merge-new-only",
151
- };
152
-
153
- if (args.json) {
154
- console.log(JSON.stringify(result, null, 2));
155
- } else {
156
- console.log(`# kb-graph-promote${args.dryRun ? " (dry-run)" : ""}`);
157
- console.log(`copied: ${result.copied_count}`);
158
- console.log(`skipped_existing: ${result.skipped_count}`);
159
- for (const c of stats.copied) console.log(`+ ${c}`);
160
- for (const s of stats.skipped) console.log(`= ${s}`);
161
- }
162
- }
163
-
164
- main();
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Promote knowledge/bootstrap/staging → formal trees (merge-new-only).
4
+ * Never overwrites existing formal files. Does not call an LLM.
5
+ */
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ import process from "node:process";
9
+
10
+ function parseArgs(argv) {
11
+ const out = {
12
+ root: process.cwd(),
13
+ from: "knowledge/bootstrap/staging",
14
+ dryRun: false,
15
+ json: true,
16
+ };
17
+ for (let i = 0; i < argv.length; i++) {
18
+ const a = argv[i];
19
+ if (a === "--root") out.root = path.resolve(argv[++i]);
20
+ else if (a === "--from") out.from = argv[++i];
21
+ else if (a === "--dry-run") out.dryRun = true;
22
+ else if (a === "--json") out.json = true;
23
+ else if (a === "--markdown") out.json = false;
24
+ else if (a === "-h" || a === "--help") {
25
+ console.log(
26
+ "Usage: node scripts/kb-graph-promote.mjs [--root <dir>] [--from <staging-rel>] [--dry-run] [--json|--markdown]",
27
+ );
28
+ process.exit(0);
29
+ } else {
30
+ throw new Error(`unknown argument: ${a}`);
31
+ }
32
+ }
33
+ return out;
34
+ }
35
+
36
+ function copyDirMergeNew(src, dest, dryRun, stats) {
37
+ if (!fs.existsSync(src)) return;
38
+ fs.mkdirSync(dest, { recursive: true });
39
+ for (const ent of fs.readdirSync(src, { withFileTypes: true })) {
40
+ const s = path.join(src, ent.name);
41
+ const d = path.join(dest, ent.name);
42
+ if (ent.isDirectory()) {
43
+ copyDirMergeNew(s, d, dryRun, stats);
44
+ continue;
45
+ }
46
+ if (fs.existsSync(d)) {
47
+ stats.skipped.push(path.relative(stats.root, d).split(path.sep).join("/"));
48
+ continue;
49
+ }
50
+ stats.copied.push(path.relative(stats.root, d).split(path.sep).join("/"));
51
+ if (!dryRun) {
52
+ fs.mkdirSync(path.dirname(d), { recursive: true });
53
+ fs.copyFileSync(s, d);
54
+ }
55
+ }
56
+ }
57
+
58
+ function featureIdFromProposalFile(fileName) {
59
+ if (!/^F-[A-Za-z0-9][A-Za-z0-9._-]*\.ya?ml$/.test(fileName)) {
60
+ throw new Error(`invalid feature link proposal filename: ${fileName}`);
61
+ }
62
+ const id = fileName.replace(/\.ya?ml$/, "");
63
+ if (id.includes("..")) {
64
+ throw new Error(`invalid feature link proposal filename: ${fileName}`);
65
+ }
66
+ return id;
67
+ }
68
+
69
+ function main() {
70
+ let args;
71
+ try {
72
+ args = parseArgs(process.argv.slice(2));
73
+ } catch (e) {
74
+ console.error(String(e?.message || e));
75
+ process.exit(2);
76
+ }
77
+ const root = args.root;
78
+ const staging = path.join(root, args.from);
79
+ if (!fs.existsSync(staging)) {
80
+ const err = {
81
+ ok: false,
82
+ error: `missing staging at ${args.from}`,
83
+ };
84
+ console.log(JSON.stringify(err, null, 2));
85
+ process.exit(1);
86
+ }
87
+
88
+ const stats = { root, copied: [], skipped: [] };
89
+ copyDirMergeNew(
90
+ path.join(staging, "domains"),
91
+ path.join(root, "knowledge/domains"),
92
+ args.dryRun,
93
+ stats,
94
+ );
95
+ copyDirMergeNew(
96
+ path.join(staging, "services"),
97
+ path.join(root, "knowledge/services"),
98
+ args.dryRun,
99
+ stats,
100
+ );
101
+ copyDirMergeNew(
102
+ path.join(staging, "modules"),
103
+ path.join(root, "knowledge/modules"),
104
+ args.dryRun,
105
+ stats,
106
+ );
107
+
108
+ const edgesSrc = path.join(staging, "edges.proposed.yaml");
109
+ const edgesManual = path.join(root, "knowledge/graph/edges.manual.yaml");
110
+ if (fs.existsSync(edgesSrc)) {
111
+ if (fs.existsSync(edgesManual)) {
112
+ stats.skipped.push("knowledge/graph/edges.manual.yaml");
113
+ } else {
114
+ stats.copied.push("knowledge/graph/edges.manual.yaml");
115
+ if (!args.dryRun) {
116
+ fs.mkdirSync(path.dirname(edgesManual), { recursive: true });
117
+ fs.copyFileSync(edgesSrc, edgesManual);
118
+ }
119
+ }
120
+ }
121
+
122
+ const linksDir = path.join(staging, "features-links.proposed");
123
+ if (fs.existsSync(linksDir)) {
124
+ for (const f of fs.readdirSync(linksDir)) {
125
+ if (!f.endsWith(".yaml") && !f.endsWith(".yml")) continue;
126
+ const id = featureIdFromProposalFile(f);
127
+ const featureDir = path.join(root, "features", id);
128
+ if (!fs.existsSync(featureDir)) continue;
129
+ const dest = path.join(featureDir, "knowledge-links.yaml");
130
+ const rel = `features/${id}/knowledge-links.yaml`;
131
+ if (fs.existsSync(dest)) {
132
+ stats.skipped.push(rel);
133
+ } else {
134
+ stats.copied.push(rel);
135
+ if (!args.dryRun) {
136
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
137
+ fs.copyFileSync(path.join(linksDir, f), dest);
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ const result = {
144
+ ok: true,
145
+ dry_run: args.dryRun,
146
+ copied_count: stats.copied.length,
147
+ skipped_count: stats.skipped.length,
148
+ copied: stats.copied,
149
+ skipped_existing: stats.skipped,
150
+ policy: "merge-new-only",
151
+ };
152
+
153
+ if (args.json) {
154
+ console.log(JSON.stringify(result, null, 2));
155
+ } else {
156
+ console.log(`# kb-graph-promote${args.dryRun ? " (dry-run)" : ""}`);
157
+ console.log(`copied: ${result.copied_count}`);
158
+ console.log(`skipped_existing: ${result.skipped_count}`);
159
+ for (const c of stats.copied) console.log(`+ ${c}`);
160
+ for (const s of stats.skipped) console.log(`= ${s}`);
161
+ }
162
+ }
163
+
164
+ main();