@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,278 @@
1
+ #!/usr/bin/env node
2
+ // Persistent wait daemon. It watches wakeup.json files created by the
3
+ // persistent-mode hook and resumes only sessions that declare a safe engine.
4
+
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { spawnSync } from 'node:child_process';
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ const ROOT = path.resolve(__dirname, '..', '..');
12
+ const PIDFILE = path.join(ROOT, '.harness', 'wait.pid');
13
+ const POLL_MS = Number(process.env.HARNESS_WAIT_POLL_MS || 10_000);
14
+ const BACKOFF_MS = Number(process.env.HARNESS_WAIT_BACKOFF_MS || 60_000);
15
+
16
+ if (isMain()) main(process.argv.slice(2));
17
+
18
+ function main(argv) {
19
+ const verb = argv[0] || 'status';
20
+ if (verb === 'start') start({ root: ROOT });
21
+ else if (verb === 'stop') stop({ root: ROOT });
22
+ else if (verb === 'status') status({ root: ROOT });
23
+ else { console.error(`unknown wait command: ${verb}. start | stop | status`); process.exit(2); }
24
+ }
25
+
26
+ function start({ root = ROOT } = {}) {
27
+ if (fs.existsSync(PIDFILE)) {
28
+ const pid = Number(fs.readFileSync(PIDFILE, 'utf8'));
29
+ if (alive(pid)) { console.error(`already running: pid=${pid}`); process.exit(1); }
30
+ fs.unlinkSync(PIDFILE);
31
+ }
32
+ fs.mkdirSync(path.dirname(PIDFILE), { recursive: true });
33
+ fs.writeFileSync(PIDFILE, String(process.pid));
34
+ console.log(`[harness wait] start pid=${process.pid} poll=${POLL_MS}ms`);
35
+
36
+ const interval = setInterval(() => tick({ root }), POLL_MS);
37
+ process.on('SIGTERM', cleanup);
38
+ process.on('SIGINT', cleanup);
39
+ function cleanup() {
40
+ clearInterval(interval);
41
+ try { fs.unlinkSync(PIDFILE); } catch {}
42
+ console.log('[harness wait] stop');
43
+ process.exit(0);
44
+ }
45
+ tick({ root });
46
+ }
47
+
48
+ function stop() {
49
+ if (!fs.existsSync(PIDFILE)) { console.log('(daemon not running)'); return; }
50
+ const pid = Number(fs.readFileSync(PIDFILE, 'utf8'));
51
+ if (alive(pid)) {
52
+ try { process.kill(pid, 'SIGTERM'); console.log(`SIGTERM pid=${pid}`); }
53
+ catch (e) { console.error('kill failed:', e.message); }
54
+ } else {
55
+ console.log(`stale pidfile (pid=${pid}). cleaning.`);
56
+ }
57
+ try { fs.unlinkSync(PIDFILE); } catch {}
58
+ }
59
+
60
+ function status({ root = ROOT } = {}) {
61
+ if (!fs.existsSync(PIDFILE)) console.log('daemon stopped');
62
+ else {
63
+ const pid = Number(fs.readFileSync(PIDFILE, 'utf8'));
64
+ console.log(`pid=${pid} alive=${alive(pid)}`);
65
+ }
66
+ console.log(`pending wakeup: ${countWakeups(sessionsDir(root))}`);
67
+ }
68
+
69
+ function alive(pid) {
70
+ try { process.kill(pid, 0); return true; }
71
+ catch { return false; }
72
+ }
73
+
74
+ function tick({ root = ROOT, runner = spawnSync, now = new Date() } = {}) {
75
+ const decisions = processWakeups({ root, runner, now });
76
+ for (const d of decisions) {
77
+ if (d.action === 'resumed') console.log(`[wait] ${d.sessionId}: resumed ${d.command.join(' ')}`);
78
+ else if (d.action === 'backoff') console.log(`[wait] ${d.sessionId}: resume failed; backoff until ${d.notBefore}`);
79
+ else if (d.action === 'blocked-human-gate') console.log(`[wait] ${d.sessionId}: HUMAN_GATE; skipped`);
80
+ else if (d.action === 'blocked') console.log(`[wait] ${d.sessionId}: blocked (${d.reason})`);
81
+ else if (d.action === 'cleared-inactive') console.log(`[wait] ${d.sessionId}: inactive; wakeup cleared`);
82
+ }
83
+ return decisions;
84
+ }
85
+
86
+ function processWakeups({ root = ROOT, runner = spawnSync, now = new Date() } = {}) {
87
+ const dir = sessionsDir(root);
88
+ if (!fs.existsSync(dir)) return [];
89
+ const decisions = [];
90
+ for (const sessionId of fs.readdirSync(dir).sort()) {
91
+ const sessionDir = path.join(dir, sessionId);
92
+ if (!fs.statSync(sessionDir).isDirectory()) continue;
93
+ const wakeupPath = path.join(sessionDir, 'wakeup.json');
94
+ if (!fs.existsSync(wakeupPath)) continue;
95
+
96
+ const wakeup = readJson(wakeupPath) || {};
97
+ if (wakeup.not_before && new Date(wakeup.not_before) > now) {
98
+ decisions.push(writeDecision(sessionDir, { sessionId, action: 'waiting-backoff', notBefore: wakeup.not_before }));
99
+ continue;
100
+ }
101
+
102
+ const activePath = path.join(sessionDir, 'active');
103
+ if (!fs.existsSync(activePath)) {
104
+ removeFile(wakeupPath);
105
+ decisions.push(writeDecision(sessionDir, { sessionId, action: 'cleared-inactive', reason: 'active file missing' }));
106
+ continue;
107
+ }
108
+
109
+ if (fs.existsSync(path.join(sessionDir, 'HUMAN_GATE'))) {
110
+ removeFile(wakeupPath);
111
+ decisions.push(writeDecision(sessionDir, { sessionId, action: 'blocked-human-gate', reason: 'HUMAN_GATE exists' }));
112
+ continue;
113
+ }
114
+
115
+ const active = parseActiveFile(fs.readFileSync(activePath, 'utf8'));
116
+ const plan = buildResumePlan({ root, sessionId, active });
117
+ if (!plan.ok) {
118
+ removeFile(wakeupPath);
119
+ decisions.push(writeDecision(sessionDir, { sessionId, action: 'blocked', reason: plan.reason, active }));
120
+ continue;
121
+ }
122
+
123
+ const result = runner(process.execPath, plan.args, {
124
+ cwd: root,
125
+ encoding: 'utf8',
126
+ windowsHide: true,
127
+ env: { ...process.env, HARNESS_WAIT_RESUME: '1' },
128
+ });
129
+
130
+ if ((result.status ?? 1) === 0) {
131
+ removeFile(wakeupPath);
132
+ decisions.push(writeDecision(sessionDir, {
133
+ sessionId,
134
+ action: 'resumed',
135
+ command: [process.execPath, ...plan.args],
136
+ status: result.status ?? 0,
137
+ stdout: trimOutput(result.stdout),
138
+ stderr: trimOutput(result.stderr),
139
+ }));
140
+ } else {
141
+ const notBefore = new Date(now.getTime() + BACKOFF_MS).toISOString();
142
+ fs.writeFileSync(wakeupPath, JSON.stringify({ ...wakeup, not_before: notBefore, last_error: trimOutput(result.stderr || result.stdout) }, null, 2));
143
+ decisions.push(writeDecision(sessionDir, {
144
+ sessionId,
145
+ action: 'backoff',
146
+ command: [process.execPath, ...plan.args],
147
+ status: result.status ?? 1,
148
+ notBefore,
149
+ stdout: trimOutput(result.stdout),
150
+ stderr: trimOutput(result.stderr),
151
+ }));
152
+ }
153
+ }
154
+ return decisions;
155
+ }
156
+
157
+ function buildResumePlan({ root = ROOT, sessionId, active }) {
158
+ const mode = normalizeMode(active.mode);
159
+ const task = active.task || active.prompt || active.request;
160
+ if (!mode) return { ok: false, reason: 'active mode is missing or unsupported' };
161
+ if (!task) return { ok: false, reason: 'active task is missing' };
162
+
163
+ const cli = path.join('scripts', 'cli.js');
164
+ if (mode === 'ralph') {
165
+ const engine = active.engine || 'review';
166
+ if (!['review', 'legacy-review', 'run'].includes(engine)) return { ok: false, reason: `unsupported ralph engine: ${engine}` };
167
+ const args = [cli, 'ralph', task, '--session', sessionId, '--engine', engine];
168
+ if (active.max_iter || active.maxIter) args.push('--max-iter', String(active.max_iter || active.maxIter));
169
+ appendCommonFlags(args, active);
170
+ return { ok: true, args };
171
+ }
172
+
173
+ if (mode === 'run') {
174
+ const args = [cli, 'run', task, '--session', sessionId];
175
+ appendCommonFlags(args, active);
176
+ return { ok: true, args };
177
+ }
178
+
179
+ if (mode === 'review-cycle') {
180
+ const args = [cli, 'review-cycle', task, '--session', sessionId];
181
+ appendCommonFlags(args, active);
182
+ if (truthy(active.no_ship) || truthy(active.noShip)) args.push('--no-ship');
183
+ if (truthy(active.no_codex) || truthy(active.noCodex)) args.push('--no-codex');
184
+ return { ok: true, args };
185
+ }
186
+
187
+ return { ok: false, reason: `unsupported active mode: ${active.mode}` };
188
+ }
189
+
190
+ function appendCommonFlags(args, active) {
191
+ if (truthy(active.live)) args.push('--live');
192
+ if (truthy(active.secure)) args.push('--secure');
193
+ }
194
+
195
+ function normalizeMode(mode) {
196
+ if (mode === 'review') return 'review-cycle';
197
+ if (mode === 'review-cycle' || mode === 'run' || mode === 'ralph') return mode;
198
+ return null;
199
+ }
200
+
201
+ function parseActiveFile(content) {
202
+ const text = String(content || '').trim();
203
+ if (!text) return {};
204
+ if (text.startsWith('{')) {
205
+ try { return JSON.parse(text); } catch {}
206
+ }
207
+
208
+ const data = {};
209
+ for (const line of text.split(/\r?\n/)) {
210
+ const trimmed = line.trim();
211
+ if (!trimmed || trimmed.startsWith('#')) continue;
212
+ const idx = trimmed.indexOf(':');
213
+ if (idx === -1) continue;
214
+ const key = trimmed.slice(0, idx).trim();
215
+ const rawValue = trimmed.slice(idx + 1).trim();
216
+ data[key] = parseActiveValue(rawValue);
217
+ }
218
+ return data;
219
+ }
220
+
221
+ function parseActiveValue(value) {
222
+ if (value === 'true') return true;
223
+ if (value === 'false') return false;
224
+ if (/^-?\d+$/.test(value)) return Number(value);
225
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('[') && value.endsWith(']'))) {
226
+ try { return JSON.parse(value); } catch {}
227
+ }
228
+ return value;
229
+ }
230
+
231
+ function truthy(value) {
232
+ return value === true || value === 'true' || value === '1' || value === 1;
233
+ }
234
+
235
+ function sessionsDir(root) {
236
+ return path.join(root, '.harness', 'state', 'sessions');
237
+ }
238
+
239
+ function countWakeups(dir) {
240
+ if (!fs.existsSync(dir)) return 0;
241
+ return fs.readdirSync(dir)
242
+ .filter(s => fs.existsSync(path.join(dir, s, 'wakeup.json')))
243
+ .length;
244
+ }
245
+
246
+ function writeDecision(sessionDir, decision) {
247
+ const full = {
248
+ ...decision,
249
+ at: new Date().toISOString(),
250
+ };
251
+ fs.writeFileSync(path.join(sessionDir, 'wait-summary.json'), JSON.stringify(full, null, 2));
252
+ fs.appendFileSync(path.join(sessionDir, 'wait-events.jsonl'), JSON.stringify(full) + '\n');
253
+ return full;
254
+ }
255
+
256
+ function readJson(file) {
257
+ try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return null; }
258
+ }
259
+
260
+ function removeFile(file) {
261
+ try { fs.unlinkSync(file); } catch {}
262
+ }
263
+
264
+ function trimOutput(value, max = 2000) {
265
+ const text = String(value || '').trim();
266
+ return text.length > max ? `${text.slice(0, max)}...` : text;
267
+ }
268
+
269
+ function isMain() {
270
+ return path.resolve(process.argv[1] || '') === fileURLToPath(import.meta.url);
271
+ }
272
+
273
+ export {
274
+ parseActiveFile,
275
+ buildResumePlan,
276
+ processWakeups,
277
+ tick,
278
+ };
@@ -0,0 +1,222 @@
1
+ #!/usr/bin/env node
2
+ // Create a tiny target project and run the repository-based NEKOWORK porting path.
3
+
4
+ import { spawnSync } from 'node:child_process';
5
+ import fs from 'node:fs';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ const ROOT = path.resolve(__dirname, '..');
12
+
13
+ function parseArgs(argv) {
14
+ const args = {
15
+ cleanup: false,
16
+ force: false,
17
+ profile: 'developer',
18
+ session: 'demo-external-project',
19
+ target: null,
20
+ task: 'demo external project smoke',
21
+ };
22
+
23
+ for (let i = 0; i < argv.length; i++) {
24
+ const arg = argv[i];
25
+ if (arg === '--cleanup') args.cleanup = true;
26
+ else if (arg === '--force') args.force = true;
27
+ else if (arg === '--profile') args.profile = takeValue(argv, ++i, arg);
28
+ else if (arg.startsWith('--profile=')) args.profile = arg.slice('--profile='.length);
29
+ else if (arg === '--session') args.session = takeValue(argv, ++i, arg);
30
+ else if (arg.startsWith('--session=')) args.session = arg.slice('--session='.length);
31
+ else if (arg === '--target') args.target = takeValue(argv, ++i, arg);
32
+ else if (arg.startsWith('--target=')) args.target = arg.slice('--target='.length);
33
+ else if (arg === '--task') args.task = takeValue(argv, ++i, arg);
34
+ else if (arg.startsWith('--task=')) args.task = arg.slice('--task='.length);
35
+ else if (arg === '--help' || arg === '-h') {
36
+ printHelp();
37
+ process.exit(0);
38
+ } else {
39
+ throw new Error(`unknown option: ${arg}`);
40
+ }
41
+ }
42
+
43
+ return args;
44
+ }
45
+
46
+ function takeValue(argv, index, flag) {
47
+ const value = argv[index];
48
+ if (!value || value.startsWith('--')) throw new Error(`${flag} requires a value`);
49
+ return value;
50
+ }
51
+
52
+ function printHelp() {
53
+ console.log(`NEKOWORK external project demo
54
+
55
+ Usage:
56
+ node scripts/demo-external-project.js [--target <dir>] [--profile developer] [--session <id>] [--task <text>] [--cleanup] [--force]
57
+
58
+ What it does:
59
+ 1. Creates a tiny target project.
60
+ 2. Runs the porting preflight.
61
+ 3. Applies generated harness outputs into the target.
62
+ 4. Runs doctor in the target context.
63
+ 5. Runs a planning-only smoke session.
64
+
65
+ The demo uses mock providers by default and does not call paid APIs.
66
+ `);
67
+ }
68
+
69
+ function ensureUsableTarget(target, force) {
70
+ fs.mkdirSync(target, { recursive: true });
71
+ const entries = fs.readdirSync(target).filter(name => name !== '.git');
72
+ if (entries.length && !force) {
73
+ throw new Error(`target is not empty: ${target}. Use --force or choose an empty directory.`);
74
+ }
75
+ }
76
+
77
+ function writeSampleProject(target) {
78
+ fs.mkdirSync(path.join(target, 'src'), { recursive: true });
79
+ writeFile(path.join(target, 'README.md'), [
80
+ '# NEKOWORK Demo Target',
81
+ '',
82
+ 'This tiny project is generated by `scripts/demo-external-project.js`.',
83
+ '',
84
+ ].join('\n'));
85
+ writeFile(path.join(target, 'package.json'), JSON.stringify({
86
+ name: 'nekowork-demo-target',
87
+ version: '0.0.0',
88
+ private: true,
89
+ type: 'module',
90
+ scripts: {
91
+ test: 'node src/index.js',
92
+ },
93
+ }, null, 2) + '\n');
94
+ writeFile(path.join(target, 'src', 'index.js'), [
95
+ 'export function greet(name = "NEKOWORK") {',
96
+ ' return `hello ${name}`;',
97
+ '}',
98
+ '',
99
+ 'if (import.meta.url === `file://${process.argv[1]}`) {',
100
+ ' console.log(greet());',
101
+ '}',
102
+ '',
103
+ ].join('\n'));
104
+ }
105
+
106
+ function writeFile(file, content) {
107
+ fs.mkdirSync(path.dirname(file), { recursive: true });
108
+ fs.writeFileSync(file, content);
109
+ }
110
+
111
+ function runStep(label, command, args, options = {}) {
112
+ process.stdout.write(`${label} ... `);
113
+ const result = spawnSync(command, args, {
114
+ cwd: options.cwd || ROOT,
115
+ env: { ...process.env, FORCE_COLOR: '0', ...(options.env || {}) },
116
+ encoding: 'utf8',
117
+ timeout: options.timeoutMs || 120000,
118
+ });
119
+
120
+ if (result.status !== 0) {
121
+ process.stdout.write('FAIL\n');
122
+ const detail = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
123
+ throw new Error(`${label} failed with exit ${result.status}\n${detail}`);
124
+ }
125
+
126
+ process.stdout.write('OK\n');
127
+ return result;
128
+ }
129
+
130
+ function maybeInitGit(target) {
131
+ const result = spawnSync('git', ['init'], {
132
+ cwd: target,
133
+ encoding: 'utf8',
134
+ timeout: 10000,
135
+ });
136
+ return result.status === 0;
137
+ }
138
+
139
+ function assertExists(target, relativePath) {
140
+ const file = path.join(target, relativePath);
141
+ if (!fs.existsSync(file)) throw new Error(`expected output missing: ${relativePath}`);
142
+ }
143
+
144
+ function main() {
145
+ const args = parseArgs(process.argv.slice(2));
146
+ const target = path.resolve(args.target || fs.mkdtempSync(path.join(os.tmpdir(), 'nekowork-demo-target-')));
147
+
148
+ console.log('NEKOWORK external project demo');
149
+ console.log(`target : ${target}`);
150
+ console.log(`profile: ${args.profile}`);
151
+ console.log('');
152
+
153
+ try {
154
+ ensureUsableTarget(target, args.force);
155
+ writeSampleProject(target);
156
+ const gitReady = maybeInitGit(target);
157
+ console.log(`git : ${gitReady ? 'initialized' : 'not available; continuing without git'}`);
158
+
159
+ runStep('preflight', process.execPath, [
160
+ path.join(ROOT, 'scripts/portability/simulate-port.js'),
161
+ target,
162
+ '--profile',
163
+ args.profile,
164
+ '--json',
165
+ ]);
166
+ runStep('install apply', process.execPath, [
167
+ path.join(ROOT, 'scripts/install-apply.js'),
168
+ '--profile',
169
+ args.profile,
170
+ '--project-root',
171
+ target,
172
+ ]);
173
+ runStep('doctor', process.execPath, [
174
+ path.join(ROOT, 'scripts/cli.js'),
175
+ 'doctor',
176
+ '--project-root',
177
+ target,
178
+ '--quick',
179
+ ]);
180
+ runStep('plan smoke', process.execPath, [
181
+ path.join(ROOT, 'scripts/cli.js'),
182
+ 'plan',
183
+ args.task,
184
+ '--project-root',
185
+ target,
186
+ '--session',
187
+ args.session,
188
+ ]);
189
+
190
+ for (const file of [
191
+ '.harness/install-state.json',
192
+ `.harness/state/sessions/${args.session}/handoffs/02-plan.json`,
193
+ '.claude/CLAUDE.md',
194
+ '.codex/config.toml',
195
+ '.cursor/hooks.json',
196
+ '.gemini/GEMINI.md',
197
+ '.opencode/config.json',
198
+ ]) {
199
+ assertExists(target, file);
200
+ }
201
+
202
+ console.log('');
203
+ console.log('Demo completed.');
204
+ console.log(`Inspect target: ${target}`);
205
+ if (args.cleanup) {
206
+ fs.rmSync(target, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
207
+ console.log('Target removed because --cleanup was set.');
208
+ }
209
+ } catch (error) {
210
+ if (args.cleanup && fs.existsSync(target)) {
211
+ fs.rmSync(target, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
212
+ }
213
+ throw error;
214
+ }
215
+ }
216
+
217
+ try {
218
+ main();
219
+ } catch (error) {
220
+ console.error(error.message);
221
+ process.exit(1);
222
+ }
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+ // Run the shortest NEKOWORK experience against a disposable target project.
3
+
4
+ import { spawnSync } from 'node:child_process';
5
+ import fs from 'node:fs';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ const ROOT = path.resolve(__dirname, '..');
12
+
13
+ function parseArgs(argv) {
14
+ const args = {
15
+ cleanup: false,
16
+ profile: 'quality',
17
+ session: 'demo-quick-run',
18
+ target: null,
19
+ task: 'demo quick run: prepare a safe quality smoke change',
20
+ };
21
+
22
+ for (let i = 0; i < argv.length; i++) {
23
+ const arg = argv[i];
24
+ if (arg === '--cleanup') args.cleanup = true;
25
+ else if (arg === '--profile') args.profile = takeValue(argv, ++i, arg);
26
+ else if (arg.startsWith('--profile=')) args.profile = arg.slice('--profile='.length);
27
+ else if (arg === '--session') args.session = takeValue(argv, ++i, arg);
28
+ else if (arg.startsWith('--session=')) args.session = arg.slice('--session='.length);
29
+ else if (arg === '--target') args.target = takeValue(argv, ++i, arg);
30
+ else if (arg.startsWith('--target=')) args.target = arg.slice('--target='.length);
31
+ else if (arg === '--task') args.task = takeValue(argv, ++i, arg);
32
+ else if (arg.startsWith('--task=')) args.task = arg.slice('--task='.length);
33
+ else if (arg === '--help' || arg === '-h') {
34
+ printHelp();
35
+ process.exit(0);
36
+ } else {
37
+ throw new Error(`unknown option: ${arg}`);
38
+ }
39
+ }
40
+
41
+ return args;
42
+ }
43
+
44
+ function takeValue(argv, index, flag) {
45
+ const value = argv[index];
46
+ if (!value || value.startsWith('--')) throw new Error(`${flag} requires a value`);
47
+ return value;
48
+ }
49
+
50
+ function printHelp() {
51
+ console.log(`NEKOWORK quick run demo
52
+
53
+ Usage:
54
+ node scripts/demo-quick-run.js [--profile quality] [--session <id>] [--target <dir>] [--task <text>] [--cleanup]
55
+
56
+ What it does:
57
+ 1. Creates a tiny disposable target project.
58
+ 2. Runs doctor --quick.
59
+ 3. Runs the compact workflow: run = work -> verify -> ship.
60
+ 4. Prints gate status and the generated session path.
61
+
62
+ The demo uses mock providers by default and does not call paid APIs.
63
+ `);
64
+ }
65
+
66
+ function writeSampleProject(target) {
67
+ fs.mkdirSync(path.join(target, 'src'), { recursive: true });
68
+ fs.writeFileSync(path.join(target, 'README.md'), [
69
+ '# NEKOWORK Quick Demo Target',
70
+ '',
71
+ 'Generated by `scripts/demo-quick-run.js`.',
72
+ '',
73
+ ].join('\n'));
74
+ fs.writeFileSync(path.join(target, 'package.json'), JSON.stringify({
75
+ name: 'nekowork-quick-demo-target',
76
+ version: '0.0.0',
77
+ private: true,
78
+ type: 'module',
79
+ scripts: {
80
+ test: 'node src/index.js',
81
+ },
82
+ }, null, 2) + '\n');
83
+ fs.writeFileSync(path.join(target, 'src', 'index.js'), [
84
+ 'export function status() {',
85
+ ' return "ready";',
86
+ '}',
87
+ '',
88
+ 'console.log(status());',
89
+ '',
90
+ ].join('\n'));
91
+ }
92
+
93
+ function runStep(label, args, options = {}) {
94
+ process.stdout.write(`${label} ... `);
95
+ const result = spawnSync(process.execPath, args, {
96
+ cwd: ROOT,
97
+ env: { ...process.env, FORCE_COLOR: '0', ...(options.env || {}) },
98
+ encoding: 'utf8',
99
+ timeout: options.timeoutMs || 120000,
100
+ windowsHide: true,
101
+ });
102
+
103
+ if (result.status !== 0) {
104
+ process.stdout.write('FAIL\n');
105
+ const detail = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
106
+ throw new Error(`${label} failed with exit ${result.status}\n${detail}`);
107
+ }
108
+
109
+ process.stdout.write('OK\n');
110
+ return result;
111
+ }
112
+
113
+ function assertExists(file) {
114
+ if (!fs.existsSync(file)) throw new Error(`expected output missing: ${file}`);
115
+ }
116
+
117
+ function main() {
118
+ const args = parseArgs(process.argv.slice(2));
119
+ const target = path.resolve(args.target || fs.mkdtempSync(path.join(os.tmpdir(), 'nekowork-quick-run-')));
120
+ const sessionDir = path.join(target, '.harness', 'state', 'sessions', args.session);
121
+
122
+ console.log('NEKOWORK quick run demo');
123
+ console.log(`target : ${target}`);
124
+ console.log(`session: ${args.session}`);
125
+ console.log(`profile: ${args.profile}`);
126
+ console.log('');
127
+
128
+ try {
129
+ fs.mkdirSync(target, { recursive: true });
130
+ writeSampleProject(target);
131
+
132
+ runStep('doctor', [
133
+ path.join(ROOT, 'scripts/cli.js'),
134
+ 'doctor',
135
+ '--quick',
136
+ '--project-root',
137
+ target,
138
+ ]);
139
+ runStep('run workflow', [
140
+ path.join(ROOT, 'scripts/cli.js'),
141
+ 'run',
142
+ args.task,
143
+ '--profile',
144
+ args.profile,
145
+ '--session',
146
+ args.session,
147
+ '--project-root',
148
+ target,
149
+ '--json',
150
+ ]);
151
+ runStep('gate status', [
152
+ path.join(ROOT, 'scripts/cli.js'),
153
+ 'gate',
154
+ 'status',
155
+ '--session',
156
+ args.session,
157
+ '--project-root',
158
+ target,
159
+ '--json',
160
+ ]);
161
+
162
+ for (const file of [
163
+ path.join(sessionDir, 'work-summary.json'),
164
+ path.join(sessionDir, 'verify-summary.json'),
165
+ path.join(sessionDir, 'ship-summary.json'),
166
+ path.join(sessionDir, 'run-summary.json'),
167
+ ]) {
168
+ assertExists(file);
169
+ }
170
+
171
+ const runSummary = JSON.parse(fs.readFileSync(path.join(sessionDir, 'run-summary.json'), 'utf8'));
172
+ console.log('');
173
+ console.log(`Demo completed: verdict=${runSummary.verdict}, ship_ready=${runSummary.ship_ready}, applied=${runSummary.applied}`);
174
+ console.log(`Inspect session: ${sessionDir}`);
175
+
176
+ if (args.cleanup) {
177
+ fs.rmSync(target, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
178
+ console.log('Target removed because --cleanup was set.');
179
+ }
180
+ } catch (error) {
181
+ if (args.cleanup && fs.existsSync(target)) {
182
+ fs.rmSync(target, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
183
+ }
184
+ throw error;
185
+ }
186
+ }
187
+
188
+ try {
189
+ main();
190
+ } catch (error) {
191
+ console.error(error.message);
192
+ process.exit(1);
193
+ }