@ps-neko/nekowork 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/AGENTS.md +112 -0
  2. package/CLAUDE.md +81 -0
  3. package/LICENSE +21 -0
  4. package/README.md +283 -0
  5. package/REVIEW.md +96 -0
  6. package/RULES.md +51 -0
  7. package/SOUL.md +21 -0
  8. package/WORKING-CONTEXT.md +52 -0
  9. package/agent.yaml +219 -0
  10. package/agents/architect.md +57 -0
  11. package/agents/code-reviewer.md +60 -0
  12. package/agents/codex-challenger.md +53 -0
  13. package/agents/codex-reviewer.md +56 -0
  14. package/agents/debugger.md +33 -0
  15. package/agents/doc-writer.md +51 -0
  16. package/agents/executor.md +41 -0
  17. package/agents/planner.md +49 -0
  18. package/agents/research.md +50 -0
  19. package/agents/security-reviewer.md +47 -0
  20. package/agents/test-engineer.md +41 -0
  21. package/bridge/mcp-server.js +301 -0
  22. package/commands/claude-led-codex-review.md +29 -0
  23. package/docs/ADVANCED.md +321 -0
  24. package/docs/AI-DEVELOPMENT-LIFECYCLE.md +105 -0
  25. package/docs/ARCHITECTURE.md +205 -0
  26. package/docs/AUDIT.md +114 -0
  27. package/docs/AUTH-MIGRATION.md +282 -0
  28. package/docs/CHANGELOG.md +97 -0
  29. package/docs/CLI-STAGES.md +89 -0
  30. package/docs/CODEMAPS/README.md +15 -0
  31. package/docs/CODEMAPS/agents.md +22 -0
  32. package/docs/CODEMAPS/bridge.md +18 -0
  33. package/docs/CODEMAPS/hooks.md +28 -0
  34. package/docs/CODEMAPS/manifests.md +14 -0
  35. package/docs/CODEMAPS/rules.md +22 -0
  36. package/docs/CODEMAPS/schemas.md +21 -0
  37. package/docs/CODEMAPS/scripts.md +158 -0
  38. package/docs/CODEMAPS/skills.md +29 -0
  39. package/docs/CODEMAPS/tests.md +98 -0
  40. package/docs/CORE-INVARIANTS.md +38 -0
  41. package/docs/DEMO.md +110 -0
  42. package/docs/EXAMPLE-PROJECT.md +92 -0
  43. package/docs/PORTING.md +154 -0
  44. package/docs/PRODUCT-PRINCIPLES.md +303 -0
  45. package/docs/PUBLISH-ALPHA.md +106 -0
  46. package/docs/QUICKSTART.md +344 -0
  47. package/docs/RELEASE-READINESS.md +140 -0
  48. package/docs/RISK-CLASSIFIER.md +50 -0
  49. package/docs/RUNBOOK.md +146 -0
  50. package/docs/SECURITY.md +79 -0
  51. package/docs/SETUP.md +142 -0
  52. package/docs/WHY-NEKOWORK.md +64 -0
  53. package/docs/case-studies/README.md +16 -0
  54. package/docs/case-studies/SINDRESORHUS-IS-PLAIN-OBJ.md +141 -0
  55. package/docs/dev-log/2026-04-29-p1-recovery.md +142 -0
  56. package/docs/dev-log/2026-04-29-week1-4.md +81 -0
  57. package/docs/examples/GITHUB-ACTIONS-HARDENING.md +86 -0
  58. package/docs/examples/QUALITY-LIFECYCLE-SMOKE.md +32 -0
  59. package/docs/examples/TRADING-DASHBOARD-MOCK.md +65 -0
  60. package/docs/workflows-stash/README.md +32 -0
  61. package/docs/workflows-stash/harness-review.yml +166 -0
  62. package/docs/workflows-stash/harness-validate.yml +48 -0
  63. package/examples/github-actions-hardening/.github/workflows/hardened-validate.yml +38 -0
  64. package/examples/github-actions-hardening/README.md +31 -0
  65. package/examples/github-actions-hardening/case-study/ASK.md +26 -0
  66. package/examples/github-actions-hardening/case-study/GATE_STATUS.md +28 -0
  67. package/examples/github-actions-hardening/case-study/PLAN.md +25 -0
  68. package/examples/github-actions-hardening/case-study/SHIP_READY.md +21 -0
  69. package/examples/github-actions-hardening/case-study/TASK.md +30 -0
  70. package/examples/github-actions-hardening/case-study/TEAM_HANDOFFS.md +37 -0
  71. package/examples/github-actions-hardening/case-study/VERIFY_SUMMARY.md +35 -0
  72. package/examples/github-actions-hardening/case-study/WORK_SUMMARY.md +24 -0
  73. package/examples/github-actions-hardening/package.json +12 -0
  74. package/examples/github-actions-hardening/scripts/check.mjs +43 -0
  75. package/examples/quality-lifecycle-smoke/README.md +30 -0
  76. package/examples/quality-lifecycle-smoke/case-study/ASK.md +24 -0
  77. package/examples/quality-lifecycle-smoke/case-study/GATE_STATUS.md +10 -0
  78. package/examples/quality-lifecycle-smoke/case-study/PLAN.md +19 -0
  79. package/examples/quality-lifecycle-smoke/case-study/SHIP_READY.md +11 -0
  80. package/examples/quality-lifecycle-smoke/case-study/TASK.md +19 -0
  81. package/examples/quality-lifecycle-smoke/case-study/TEAM_HANDOFFS.md +21 -0
  82. package/examples/quality-lifecycle-smoke/case-study/VERIFY_SUMMARY.md +44 -0
  83. package/examples/quality-lifecycle-smoke/case-study/WORK_SUMMARY.md +19 -0
  84. package/examples/quality-lifecycle-smoke/package.json +8 -0
  85. package/examples/quality-lifecycle-smoke/scripts/check.mjs +44 -0
  86. package/examples/trading-dashboard-mock/README.md +33 -0
  87. package/examples/trading-dashboard-mock/case-study/ASK.md +24 -0
  88. package/examples/trading-dashboard-mock/case-study/GATE_STATUS.md +28 -0
  89. package/examples/trading-dashboard-mock/case-study/PLAN.md +23 -0
  90. package/examples/trading-dashboard-mock/case-study/SHIP_READY.md +21 -0
  91. package/examples/trading-dashboard-mock/case-study/TASK.md +29 -0
  92. package/examples/trading-dashboard-mock/case-study/TEAM_HANDOFFS.md +49 -0
  93. package/examples/trading-dashboard-mock/case-study/VERIFY_SUMMARY.md +35 -0
  94. package/examples/trading-dashboard-mock/case-study/WORK_SUMMARY.md +27 -0
  95. package/examples/trading-dashboard-mock/fixtures/market.json +9 -0
  96. package/examples/trading-dashboard-mock/index.html +76 -0
  97. package/examples/trading-dashboard-mock/package.json +9 -0
  98. package/examples/trading-dashboard-mock/scripts/check.mjs +54 -0
  99. package/examples/trading-dashboard-mock/src/app.js +83 -0
  100. package/examples/trading-dashboard-mock/src/styles.css +227 -0
  101. package/hooks/hooks.json +44 -0
  102. package/hooks/scripts/config-protection.js +34 -0
  103. package/hooks/scripts/gateguard-fact-force.js +146 -0
  104. package/hooks/scripts/persistent-mode.mjs +27 -0
  105. package/hooks/scripts/pre-bash-dispatcher.js +63 -0
  106. package/hooks/scripts/quality-gate.js +106 -0
  107. package/manifests/install-components.json +195 -0
  108. package/manifests/install-modules.json +101 -0
  109. package/manifests/install-profiles.json +134 -0
  110. package/package.json +96 -0
  111. package/rules/common/coding-style.md +71 -0
  112. package/rules/common/security.md +69 -0
  113. package/rules/common/testing.md +58 -0
  114. package/rules/python/coding-style.md +80 -0
  115. package/rules/python/testing.md +86 -0
  116. package/rules/typescript/coding-style.md +97 -0
  117. package/rules/typescript/security.md +67 -0
  118. package/rules/typescript/testing.md +78 -0
  119. package/schemas/agent-yaml.schema.json +168 -0
  120. package/schemas/agent.schema.json +32 -0
  121. package/schemas/handoff.schema.json +105 -0
  122. package/schemas/hooks.schema.json +35 -0
  123. package/schemas/install-components.schema.json +46 -0
  124. package/schemas/install-modules.schema.json +39 -0
  125. package/schemas/install-profiles.schema.json +32 -0
  126. package/schemas/install-state.schema.json +42 -0
  127. package/schemas/routing.schema.json +42 -0
  128. package/schemas/skill.schema.json +19 -0
  129. package/scripts/agents/dispatch.js +144 -0
  130. package/scripts/agents/runners/claude.js +214 -0
  131. package/scripts/agents/runners/codex.js +233 -0
  132. package/scripts/agents/runners/gemini.js +92 -0
  133. package/scripts/agents/runners/mock.js +107 -0
  134. package/scripts/auth/github-import-gh.js +52 -0
  135. package/scripts/auth/github-login.js +79 -0
  136. package/scripts/auth/github-logout.js +21 -0
  137. package/scripts/auth/github-status.js +46 -0
  138. package/scripts/build-claude.js +101 -0
  139. package/scripts/build-codemaps.js +286 -0
  140. package/scripts/build-codex.js +93 -0
  141. package/scripts/build-cursor.js +132 -0
  142. package/scripts/build-gemini.js +117 -0
  143. package/scripts/build-opencode.js +117 -0
  144. package/scripts/ci/catalog.js +120 -0
  145. package/scripts/ci/check-markers.js +48 -0
  146. package/scripts/ci/security-hardening.js +270 -0
  147. package/scripts/ci/validate-agents.js +88 -0
  148. package/scripts/ci/validate-hooks.js +99 -0
  149. package/scripts/ci/validate-manifests.js +128 -0
  150. package/scripts/ci/validate-skills.js +93 -0
  151. package/scripts/cli.js +1134 -0
  152. package/scripts/core/auth-guard.js +22 -0
  153. package/scripts/core/build-roots.js +11 -0
  154. package/scripts/core/cli-resolver.js +64 -0
  155. package/scripts/core/execution-workspace.js +84 -0
  156. package/scripts/core/git-mutation-guard.js +79 -0
  157. package/scripts/core/install-state.js +125 -0
  158. package/scripts/core/json-extractor.js +32 -0
  159. package/scripts/core/subprocess.js +74 -0
  160. package/scripts/daemon/wait.js +278 -0
  161. package/scripts/demo-external-project.js +222 -0
  162. package/scripts/demo-quick-run.js +193 -0
  163. package/scripts/demo-review.js +204 -0
  164. package/scripts/doctor.js +296 -0
  165. package/scripts/install-apply.js +185 -0
  166. package/scripts/install-plan.js +411 -0
  167. package/scripts/lib/acceptance-criteria.js +105 -0
  168. package/scripts/lib/costs.js +82 -0
  169. package/scripts/lib/instincts.js +194 -0
  170. package/scripts/lib/keychain.js +85 -0
  171. package/scripts/lib/profile-policy.js +134 -0
  172. package/scripts/lib/profile-safety.js +81 -0
  173. package/scripts/lib/risk-classifier.js +145 -0
  174. package/scripts/lib/router.js +138 -0
  175. package/scripts/lib/severity.js +99 -0
  176. package/scripts/lib/token-vault.js +136 -0
  177. package/scripts/orchestrators/apply.js +225 -0
  178. package/scripts/orchestrators/ask.js +143 -0
  179. package/scripts/orchestrators/gate.js +179 -0
  180. package/scripts/orchestrators/ralph.js +179 -0
  181. package/scripts/orchestrators/review.js +452 -0
  182. package/scripts/orchestrators/run.js +151 -0
  183. package/scripts/orchestrators/ship.js +339 -0
  184. package/scripts/orchestrators/team-lite.js +270 -0
  185. package/scripts/orchestrators/team.js +244 -0
  186. package/scripts/orchestrators/verify.js +306 -0
  187. package/scripts/orchestrators/work.js +207 -0
  188. package/scripts/portability/simulate-port.js +220 -0
  189. package/scripts/repair.js +184 -0
  190. package/scripts/sync-claude-md.js +220 -0
  191. package/scripts/verify/claude-live.js +30 -0
  192. package/scripts/verify/codex-live.js +60 -0
  193. package/scripts/verify/gemini-live.js +48 -0
  194. package/scripts/verify/runtime.js +105 -0
  195. package/skills/claude-led-codex-review/SKILL.md +133 -0
  196. package/skills/plan-eng-review/SKILL.md +51 -0
  197. package/skills/porting/SKILL.md +69 -0
  198. package/skills/ralph/SKILL.md +48 -0
  199. package/skills/release-readiness/SKILL.md +62 -0
  200. package/skills/review/SKILL.md +42 -0
  201. package/skills/security-hardening/SKILL.md +59 -0
  202. package/skills/ship/SKILL.md +44 -0
  203. package/skills/tdd-workflow/SKILL.md +42 -0
@@ -0,0 +1,411 @@
1
+ #!/usr/bin/env node
2
+ // HARNESS install --plan: dry-run manifest planner.
3
+
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import YAML from 'yaml';
8
+ import Ajv2020 from 'ajv/dist/2020.js';
9
+ import addFormats from 'ajv-formats';
10
+ import { validateProfileSafety } from './lib/profile-safety.js';
11
+
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+ const ROOT = path.resolve(__dirname, '..');
14
+
15
+ function parseArgs(argv) {
16
+ const args = {
17
+ profile: null,
18
+ harness: null,
19
+ json: false,
20
+ list: false,
21
+ verbose: false,
22
+ projectRoot: null,
23
+ modules: [],
24
+ withoutModules: [],
25
+ components: [],
26
+ withoutComponents: [],
27
+ };
28
+
29
+ for (let i = 2; i < argv.length; i++) {
30
+ const a = argv[i];
31
+ if (a === '--profile') args.profile = takeValue(argv, i++, a);
32
+ else if (a === '--harness' || a === '--target') args.harness = takeValue(argv, i++, a);
33
+ else if (a === '--module' || a === '--with-module') args.modules.push(takeValue(argv, i++, a));
34
+ else if (a === '--without-module') args.withoutModules.push(takeValue(argv, i++, a));
35
+ else if (a === '--component' || a === '--with-component') args.components.push(takeValue(argv, i++, a));
36
+ else if (a === '--without-component') args.withoutComponents.push(takeValue(argv, i++, a));
37
+ else if (a === '--project-root' || a === '--target-root') args.projectRoot = takeValue(argv, i++, a);
38
+ else if (a === '--json') args.json = true;
39
+ else if (a === '--list') args.list = true;
40
+ else if (a === '--verbose' || a === '-v') args.verbose = true;
41
+ else if (a === '--help' || a === '-h') {
42
+ printHelp();
43
+ process.exit(0);
44
+ } else {
45
+ console.error(`unknown argument: ${a}`);
46
+ process.exit(2);
47
+ }
48
+ }
49
+ return args;
50
+ }
51
+
52
+ function takeValue(argv, i, flag) {
53
+ const value = argv[i + 1];
54
+ if (!value || value.startsWith('--')) {
55
+ console.error(`${flag} value required`);
56
+ process.exit(2);
57
+ }
58
+ return value;
59
+ }
60
+
61
+ function printHelp() {
62
+ console.log(`
63
+ HARNESS install --plan
64
+
65
+ Usage:
66
+ install.sh --plan [--profile <name>] [--target <name>] [--module <id>] [--component <id>] [--project-root <dir>] [--json] [--verbose]
67
+ install.sh --plan --list [--json]
68
+
69
+ Options:
70
+ --profile <name> profile to install (core | developer | security | product | quality | frontend | testing | research | full)
71
+ --target <name> harness target (claude | codex | cursor | gemini | opencode)
72
+ --harness <name> alias for --target
73
+ --module <id> include an additional module, repeatable
74
+ --without-module <id> exclude a module, repeatable
75
+ --component <id> include a direct component, repeatable
76
+ --without-component <id> exclude a component, repeatable
77
+ --project-root <dir> annotate the intended target project root (dry-run still writes nothing)
78
+ --target-root <dir> alias for --project-root
79
+ --list list available profiles, modules, components, and targets
80
+ --json emit JSON
81
+ --verbose print schema validation detail
82
+ --help show this help
83
+
84
+ Examples:
85
+ ./install.sh --plan --list
86
+ ./install.sh --plan --profile core
87
+ ./install.sh --plan --profile developer --target claude --json
88
+ ./install.sh --plan --profile core --module codex-loop --without-component hook:persistent-mode
89
+ `);
90
+ }
91
+
92
+ function readJson(rel) {
93
+ return JSON.parse(fs.readFileSync(path.join(ROOT, rel), 'utf8'));
94
+ }
95
+
96
+ function readYaml(rel) {
97
+ return YAML.parse(fs.readFileSync(path.join(ROOT, rel), 'utf8'));
98
+ }
99
+
100
+ function validateAll(verbose) {
101
+ const ajv = new Ajv2020({ allErrors: true, strict: false });
102
+ addFormats(ajv);
103
+
104
+ const checks = [
105
+ { name: 'agent.yaml', schema: 'schemas/agent-yaml.schema.json', data: readYaml('agent.yaml') },
106
+ { name: 'manifests/install-profiles', schema: 'schemas/install-profiles.schema.json', data: readJson('manifests/install-profiles.json') },
107
+ { name: 'manifests/install-modules', schema: 'schemas/install-modules.schema.json', data: readJson('manifests/install-modules.json') },
108
+ { name: 'manifests/install-components', schema: 'schemas/install-components.schema.json', data: readJson('manifests/install-components.json') },
109
+ ];
110
+
111
+ let ok = true;
112
+ for (const c of checks) {
113
+ const validate = ajv.compile(readJson(c.schema));
114
+ const valid = validate(c.data);
115
+ if (!valid) {
116
+ ok = false;
117
+ console.error(` [FAIL] ${c.name}`);
118
+ for (const err of validate.errors || []) console.error(` ${err.instancePath} ${err.message}`);
119
+ } else if (verbose) {
120
+ console.error(` [OK] ${c.name}`);
121
+ }
122
+ }
123
+ const profilesDoc = checks.find(c => c.name === 'manifests/install-profiles')?.data;
124
+ if (profilesDoc) {
125
+ const safety = validateProfileSafety(profilesDoc);
126
+ if (safety.warnings.length && verbose) {
127
+ for (const warning of safety.warnings) console.error(` [WARN] ${warning}`);
128
+ }
129
+ if (safety.errors.length) {
130
+ ok = false;
131
+ console.error(' [FAIL] profile safety');
132
+ for (const err of safety.errors) console.error(` ${err}`);
133
+ } else if (verbose) {
134
+ console.error(' [OK] profile safety');
135
+ }
136
+ }
137
+ return ok;
138
+ }
139
+
140
+ function plan(profileName, filters = {}) {
141
+ const manifest = readYaml('agent.yaml');
142
+ const profilesDoc = readJson('manifests/install-profiles.json');
143
+ const modulesDoc = readJson('manifests/install-modules.json');
144
+ const componentsDoc = readJson('manifests/install-components.json');
145
+
146
+ const resolvedProfile = profileName || manifest.profiles?.default || 'core';
147
+ const profile = profilesDoc.profiles[resolvedProfile];
148
+ if (!profile) {
149
+ throw new Error(`unknown profile: ${resolvedProfile}. available: ${Object.keys(profilesDoc.profiles).join(', ')}`);
150
+ }
151
+
152
+ validateSelections({ manifest, modulesDoc, componentsDoc, filters });
153
+
154
+ const harnessFilter = filters.harness || null;
155
+ const excludedModules = new Set(filters.withoutModules || []);
156
+ const excludedComponents = new Set(filters.withoutComponents || []);
157
+ const seen = new Set();
158
+ const queue = [...profile.modules, ...(filters.modules || [])];
159
+
160
+ while (queue.length) {
161
+ const mid = queue.shift();
162
+ if (excludedModules.has(mid) || seen.has(mid)) continue;
163
+ const def = modulesDoc.modules[mid];
164
+ if (!def) throw new Error(`module definition not found: ${mid}`);
165
+ seen.add(mid);
166
+ for (const dep of def.depends_on || []) queue.push(dep);
167
+ }
168
+
169
+ const modules = [...seen];
170
+ const componentRows = [];
171
+
172
+ for (const mid of modules) {
173
+ const def = modulesDoc.modules[mid];
174
+ for (const cid of def.components || []) {
175
+ if (excludedComponents.has(cid)) continue;
176
+ const comp = componentsDoc.components[cid];
177
+ if (!comp) {
178
+ componentRows.push({ module: mid, component: cid, type: 'missing', missing: true });
179
+ continue;
180
+ }
181
+ pushComponentRows(componentRows, mid, cid, comp, harnessFilter);
182
+ }
183
+ }
184
+
185
+ for (const cid of filters.components || []) {
186
+ if (excludedComponents.has(cid)) continue;
187
+ if (componentRows.some(r => r.component === cid)) continue;
188
+ const comp = componentsDoc.components[cid];
189
+ if (!comp) {
190
+ componentRows.push({ module: '(direct)', component: cid, type: 'missing', missing: true });
191
+ continue;
192
+ }
193
+ pushComponentRows(componentRows, '(direct)', cid, comp, harnessFilter);
194
+ }
195
+
196
+ return {
197
+ harness_version: manifest.version,
198
+ profile: resolvedProfile,
199
+ profile_description: profile.description,
200
+ profile_defaults: profile.defaults || null,
201
+ modules,
202
+ selected_modules: filters.modules || [],
203
+ excluded_modules: filters.withoutModules || [],
204
+ selected_components: filters.components || [],
205
+ excluded_components: filters.withoutComponents || [],
206
+ component_count: componentRows.length,
207
+ components: componentRows,
208
+ harness_filter: harnessFilter,
209
+ target_root: filters.projectRoot ? path.resolve(filters.projectRoot) : null,
210
+ note: 'dry-run only. Use install-apply.js --apply to build target harness outputs.',
211
+ };
212
+ }
213
+
214
+ function validateSelections({ manifest, modulesDoc, componentsDoc, filters }) {
215
+ const availableTargets = (manifest.harnesses || []).map(h => h.name);
216
+ const availableModules = Object.keys(modulesDoc.modules);
217
+ const availableComponents = Object.keys(componentsDoc.components);
218
+
219
+ if (filters.harness && !availableTargets.includes(filters.harness)) {
220
+ throw new Error(`unknown target: ${filters.harness}. available: ${availableTargets.join(', ')}`);
221
+ }
222
+
223
+ for (const mid of [...(filters.modules || []), ...(filters.withoutModules || [])]) {
224
+ if (!modulesDoc.modules[mid]) {
225
+ throw new Error(`unknown module: ${mid}. available: ${availableModules.join(', ')}`);
226
+ }
227
+ }
228
+
229
+ for (const cid of [...(filters.components || []), ...(filters.withoutComponents || [])]) {
230
+ if (!componentsDoc.components[cid]) {
231
+ throw new Error(`unknown component: ${cid}. available: ${availableComponents.join(', ')}`);
232
+ }
233
+ }
234
+ }
235
+
236
+ function pushComponentRows(componentRows, moduleName, cid, comp, harnessFilter) {
237
+ const targets = comp.target || {};
238
+ const harnesses = Object.keys(targets);
239
+ const filtered = harnessFilter ? harnesses.filter(h => h === harnessFilter) : harnesses;
240
+
241
+ if (filtered.length === 0 && harnesses.length === 0) {
242
+ const platformHarness = cid.startsWith('platform:') ? cid.slice('platform:'.length) : null;
243
+ if (harnessFilter && platformHarness && platformHarness !== harnessFilter) return;
244
+
245
+ componentRows.push({
246
+ module: moduleName,
247
+ component: cid,
248
+ type: comp.type,
249
+ source: comp.source || comp.builder || '-',
250
+ harness: '(builder)',
251
+ target: comp.output_dir || '-',
252
+ });
253
+ return;
254
+ }
255
+
256
+ for (const h of filtered) {
257
+ componentRows.push({
258
+ module: moduleName,
259
+ component: cid,
260
+ type: comp.type,
261
+ source: comp.source || '-',
262
+ harness: h,
263
+ target: targets[h],
264
+ });
265
+ }
266
+ }
267
+
268
+ function printPlan(p) {
269
+ const bold = (s) => process.stdout.isTTY ? `\x1b[1m${s}\x1b[0m` : s;
270
+ console.log('');
271
+ console.log(bold(`HARNESS install --plan (v${p.harness_version})`));
272
+ console.log(' profile : ' + p.profile);
273
+ console.log(' description : ' + p.profile_description);
274
+ if (p.harness_filter) console.log(' target : ' + p.harness_filter);
275
+ if (p.target_root) console.log(' target root : ' + p.target_root);
276
+ if (p.selected_modules.length) console.log(' with modules : ' + p.selected_modules.join(', '));
277
+ if (p.excluded_modules.length) console.log(' without mods : ' + p.excluded_modules.join(', '));
278
+ if (p.selected_components.length) console.log(' components+ : ' + p.selected_components.join(', '));
279
+ if (p.excluded_components.length) console.log(' components- : ' + p.excluded_components.join(', '));
280
+ if (p.profile_defaults) console.log(' defaults : ' + JSON.stringify(p.profile_defaults));
281
+ console.log(' modules (' + p.modules.length + ') : ' + p.modules.join(', '));
282
+ console.log(' components : ' + p.component_count);
283
+ console.log('');
284
+
285
+ const byModule = new Map();
286
+ for (const row of p.components) {
287
+ if (!byModule.has(row.module)) byModule.set(row.module, []);
288
+ byModule.get(row.module).push(row);
289
+ }
290
+
291
+ for (const [mid, rows] of byModule) {
292
+ console.log(bold(` [${mid}]`));
293
+ for (const row of rows) {
294
+ const missing = row.missing ? ' [MISSING-DEFINITION]' : '';
295
+ console.log(` - ${String(row.type).padEnd(9)} ${row.component.padEnd(30)} ${row.harness.padEnd(10)} ${row.target || ''}${missing}`);
296
+ }
297
+ }
298
+
299
+ console.log('');
300
+ console.log('NOTE: ' + p.note);
301
+ console.log('');
302
+ }
303
+
304
+ function listCatalog() {
305
+ const manifest = readYaml('agent.yaml');
306
+ const profilesDoc = readJson('manifests/install-profiles.json');
307
+ const modulesDoc = readJson('manifests/install-modules.json');
308
+ const componentsDoc = readJson('manifests/install-components.json');
309
+
310
+ return {
311
+ harness_version: manifest.version,
312
+ default_profile: manifest.profiles?.default || null,
313
+ targets: (manifest.harnesses || []).map(h => ({
314
+ name: h.name,
315
+ output_dir: h.output_dir,
316
+ builder: h.builder,
317
+ })),
318
+ profiles: Object.entries(profilesDoc.profiles).map(([name, p]) => ({
319
+ name,
320
+ description: p.description,
321
+ modules: p.modules,
322
+ defaults: p.defaults || null,
323
+ })),
324
+ modules: Object.entries(modulesDoc.modules).map(([name, m]) => ({
325
+ name,
326
+ description: m.description,
327
+ components: m.components,
328
+ depends_on: m.depends_on || [],
329
+ required: Boolean(m.required),
330
+ })),
331
+ components: Object.entries(componentsDoc.components).map(([name, c]) => ({
332
+ name,
333
+ type: c.type,
334
+ source: c.source || c.builder || null,
335
+ targets: Object.keys(c.target || {}),
336
+ output_dir: c.output_dir || null,
337
+ })),
338
+ };
339
+ }
340
+
341
+ function printCatalog(catalog) {
342
+ const bold = (s) => process.stdout.isTTY ? `\x1b[1m${s}\x1b[0m` : s;
343
+ console.log('');
344
+ console.log(bold(`HARNESS install catalog (v${catalog.harness_version})`));
345
+ console.log(' default profile: ' + catalog.default_profile);
346
+ console.log('');
347
+
348
+ console.log(bold('Targets'));
349
+ for (const t of catalog.targets) {
350
+ console.log(` - ${t.name.padEnd(8)} ${t.output_dir.padEnd(12)} ${t.builder}`);
351
+ }
352
+ console.log('');
353
+
354
+ console.log(bold('Profiles'));
355
+ for (const p of catalog.profiles) {
356
+ console.log(` - ${p.name.padEnd(10)} ${p.modules.join(', ')}`);
357
+ }
358
+ console.log('');
359
+
360
+ console.log(bold('Modules'));
361
+ for (const m of catalog.modules) {
362
+ const deps = m.depends_on.length ? ` deps=${m.depends_on.join(',')}` : '';
363
+ const required = m.required ? ' required' : '';
364
+ console.log(` - ${m.name.padEnd(18)} ${m.components.length} components${required}${deps}`);
365
+ }
366
+ console.log('');
367
+
368
+ console.log(bold('Components'));
369
+ for (const c of catalog.components) {
370
+ const targets = c.targets.length ? c.targets.join(',') : (c.output_dir ? `builder:${c.output_dir}` : '-');
371
+ console.log(` - ${c.name.padEnd(32)} ${String(c.type).padEnd(9)} ${targets}`);
372
+ }
373
+ console.log('');
374
+ }
375
+
376
+ async function main() {
377
+ const args = parseArgs(process.argv);
378
+
379
+ if (args.verbose) console.error('=> validating manifests');
380
+ if (!validateAll(args.verbose)) {
381
+ console.error('');
382
+ console.error('FAIL: manifest validation failed.');
383
+ process.exit(1);
384
+ }
385
+ if (args.verbose) console.error('=> validation passed');
386
+
387
+ if (args.list) {
388
+ const catalog = listCatalog();
389
+ if (args.json) process.stdout.write(JSON.stringify(catalog, null, 2) + '\n');
390
+ else printCatalog(catalog);
391
+ return;
392
+ }
393
+
394
+ let p;
395
+ try {
396
+ p = plan(args.profile, args);
397
+ } catch (e) {
398
+ console.error('FAIL: ' + e.message);
399
+ process.exit(1);
400
+ }
401
+
402
+ if (args.json) process.stdout.write(JSON.stringify(p, null, 2) + '\n');
403
+ else printPlan(p);
404
+ }
405
+
406
+ main().catch((e) => {
407
+ console.error('UNEXPECTED:', e?.stack || e);
408
+ process.exit(1);
409
+ });
410
+
411
+ export { plan as _plan };
@@ -0,0 +1,105 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ const DEFAULT_COUNT = 3;
5
+
6
+ export function normalizeAcceptanceCriteria(value, source = 'unknown') {
7
+ const rows = Array.isArray(value) ? value : [];
8
+ return rows
9
+ .map((row, index) => {
10
+ if (typeof row === 'string') {
11
+ return {
12
+ id: `AC-${String(index + 1).padStart(3, '0')}`,
13
+ desc: row.trim(),
14
+ passes: false,
15
+ source,
16
+ };
17
+ }
18
+ if (!row || typeof row !== 'object') return null;
19
+ const desc = String(row.desc || row.description || row.summary || '').trim();
20
+ if (!desc) return null;
21
+ return {
22
+ id: String(row.id || `AC-${String(index + 1).padStart(3, '0')}`),
23
+ desc,
24
+ passes: typeof row.passes === 'boolean' ? row.passes : false,
25
+ source: row.source || source,
26
+ };
27
+ })
28
+ .filter(Boolean);
29
+ }
30
+
31
+ export function readAcceptanceCriteria(sessionDir) {
32
+ const artifact = readJson(path.join(sessionDir, 'acceptance-criteria.json'));
33
+ if (artifact?.criteria?.length) {
34
+ return {
35
+ criteria: normalizeAcceptanceCriteria(artifact.criteria, artifact.source || 'acceptance-criteria.json'),
36
+ source: artifact.source || 'acceptance-criteria.json',
37
+ generated: Boolean(artifact.generated),
38
+ };
39
+ }
40
+
41
+ const prd = readJson(path.join(sessionDir, 'prd.json'));
42
+ const fromPrd = normalizeAcceptanceCriteria(prd?.acceptance, 'prd.json');
43
+ if (fromPrd.length) {
44
+ return { criteria: fromPrd, source: 'prd.json', generated: false };
45
+ }
46
+
47
+ const ask = readJson(path.join(sessionDir, 'ask.json'));
48
+ const fromAsk = normalizeAcceptanceCriteria(ask?.success_criteria, 'ask.json');
49
+ if (fromAsk.length) {
50
+ return { criteria: fromAsk, source: 'ask.json', generated: false };
51
+ }
52
+
53
+ const questionGate = readJson(path.join(sessionDir, 'handoffs', '00-question-gate.json'));
54
+ const fromGate = normalizeAcceptanceCriteria(questionGate?.success_criteria, '00-question-gate.json');
55
+ if (fromGate.length) {
56
+ return { criteria: fromGate, source: '00-question-gate.json', generated: false };
57
+ }
58
+
59
+ return { criteria: [], source: null, generated: false };
60
+ }
61
+
62
+ export function ensureAcceptanceCriteria({ sessionDir, task, minimum = DEFAULT_COUNT }) {
63
+ fs.mkdirSync(sessionDir, { recursive: true });
64
+ const existing = readAcceptanceCriteria(sessionDir);
65
+ const criteria = existing.criteria.length
66
+ ? existing.criteria
67
+ : buildDefaultAcceptanceCriteria(task, minimum);
68
+ const source = existing.source || 'task-derived-minimum';
69
+ const generated = existing.criteria.length ? existing.generated : true;
70
+
71
+ const artifact = {
72
+ source,
73
+ generated,
74
+ required: true,
75
+ criteria,
76
+ updated_at: new Date().toISOString(),
77
+ };
78
+ fs.writeFileSync(path.join(sessionDir, 'acceptance-criteria.json'), JSON.stringify(artifact, null, 2));
79
+
80
+ return artifact;
81
+ }
82
+
83
+ export function buildDefaultAcceptanceCriteria(task = '', minimum = DEFAULT_COUNT) {
84
+ const cleanTask = String(task || 'requested change').trim() || 'requested change';
85
+ const criteria = [
86
+ `Requested outcome is implemented for: ${cleanTask}`,
87
+ 'Out-of-scope behavior is left unchanged or explicitly documented.',
88
+ 'Verification evidence is recorded before ship or apply.',
89
+ ];
90
+ return criteria.slice(0, Math.max(1, minimum)).map((desc, index) => ({
91
+ id: `AC-${String(index + 1).padStart(3, '0')}`,
92
+ desc,
93
+ passes: false,
94
+ source: 'task-derived-minimum',
95
+ }));
96
+ }
97
+
98
+ function readJson(file) {
99
+ if (!fs.existsSync(file)) return null;
100
+ try {
101
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
102
+ } catch {
103
+ return null;
104
+ }
105
+ }
@@ -0,0 +1,82 @@
1
+ // 비용 트래커. 매 도구 호출 후 모델·토큰·USD 추정값을 ~/.harness/costs.jsonl 에 append.
2
+ // CLI 조회: harness costs --since=7d (또는 --since=1h, 30m, all).
3
+
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import os from 'node:os';
7
+
8
+ const PRICE_PER_MTOK = {
9
+ // 단위: USD per 1M tokens. (대략값. 정확도는 공식 가격표 확인.)
10
+ 'claude-opus-4-7': { in: 15, out: 75 },
11
+ 'claude-sonnet-4-6': { in: 3, out: 15 },
12
+ 'claude-haiku-4-5-20251001': { in: 1, out: 5 },
13
+ opus: { in: 15, out: 75 },
14
+ sonnet: { in: 3, out: 15 },
15
+ haiku: { in: 1, out: 5 },
16
+ 'gpt-5-codex': { in: 3, out: 15 }, // 추정
17
+ 'gemini-2.5-pro': { in: 1.25, out: 5 },
18
+ mock: { in: 0, out: 0 },
19
+ };
20
+
21
+ function home() { return process.env.HARNESS_HOME || path.join(os.homedir(), '.harness'); }
22
+ function costsFile() { return path.join(home(), 'costs.jsonl'); }
23
+
24
+ export function record(entry) {
25
+ const home_ = home();
26
+ fs.mkdirSync(home_, { recursive: true });
27
+ const model = entry.model || 'mock';
28
+ const price = PRICE_PER_MTOK[model] || { in: 0, out: 0 };
29
+ const inTok = Number(entry.input_tokens || 0);
30
+ const outTok = Number(entry.output_tokens || 0);
31
+ const usd = (inTok * price.in + outTok * price.out) / 1_000_000;
32
+ const row = {
33
+ ts: entry.ts || new Date().toISOString(),
34
+ session: entry.session || 'default',
35
+ stage: entry.stage,
36
+ agent: entry.agent,
37
+ provider: entry.provider,
38
+ model,
39
+ input_tokens: inTok,
40
+ output_tokens: outTok,
41
+ duration_ms: entry.duration_ms || 0,
42
+ estimate_usd: round2(usd),
43
+ };
44
+ fs.appendFileSync(costsFile(), JSON.stringify(row) + '\n');
45
+ return row;
46
+ }
47
+
48
+ export function list({ since = '7d' } = {}) {
49
+ const f = costsFile();
50
+ if (!fs.existsSync(f)) return [];
51
+ const rows = fs.readFileSync(f, 'utf8').split('\n').filter(Boolean).map(JSON.parse);
52
+ if (since === 'all') return rows;
53
+ const cutoff = parseSince(since);
54
+ return rows.filter(r => new Date(r.ts).getTime() >= cutoff);
55
+ }
56
+
57
+ export function summarize(rows) {
58
+ const total = rows.reduce((s, r) => s + (r.estimate_usd || 0), 0);
59
+ const byModel = {};
60
+ const byProvider = {};
61
+ for (const r of rows) {
62
+ byModel[r.model] = round2((byModel[r.model] || 0) + (r.estimate_usd || 0));
63
+ byProvider[r.provider] = round2((byProvider[r.provider] || 0) + (r.estimate_usd || 0));
64
+ }
65
+ return {
66
+ rows: rows.length,
67
+ total_usd: round2(total),
68
+ by_model: byModel,
69
+ by_provider: byProvider,
70
+ };
71
+ }
72
+
73
+ function parseSince(s) {
74
+ // 1h, 30m, 7d, 24h ...
75
+ const m = String(s).match(/^(\d+)\s*([smhd])$/);
76
+ if (!m) return Date.now() - 7 * 86400_000;
77
+ const n = Number(m[1]);
78
+ const mult = { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[m[2]] || 86_400_000;
79
+ return Date.now() - n * mult;
80
+ }
81
+
82
+ function round2(n) { return Math.round(n * 100) / 100; }