tribunal-kit 5.7.0 → 5.8.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.
- package/.agent/ARCHITECTURE.md +6 -7
- package/.agent/agents/frontend-reviewer.md +13 -0
- package/.agent/agents/frontend-specialist.md +14 -0
- package/.agent/agents/logic-reviewer.md +11 -0
- package/.agent/agents/orchestrator.md +15 -0
- package/.agent/agents/security-auditor.md +13 -0
- package/.agent/agents/ui-ux-auditor.md +7 -31
- package/.agent/history/memory/.memory.idx +766 -0
- package/.agent/history/memory/MEMORY.md +62 -0
- package/.agent/routing_index.json +694 -714
- package/.agent/rules/GEMINI.md +58 -8
- package/.agent/scripts/_colors.js +131 -89
- package/.agent/scripts/_utils.js +163 -128
- package/.agent/scripts/auto_preview.js +207 -197
- package/.agent/scripts/bundle_analyzer.js +227 -192
- package/.agent/scripts/case_law_manager.js +991 -689
- package/.agent/scripts/checklist.js +233 -190
- package/.agent/scripts/context_broker.js +930 -605
- package/.agent/scripts/dependency_analyzer.js +275 -184
- package/.agent/scripts/graph_builder.js +412 -341
- package/.agent/scripts/graph_visualizer.js +392 -390
- package/.agent/scripts/graph_zoom.js +198 -156
- package/.agent/scripts/inner_loop_validator.js +523 -445
- package/.agent/scripts/lint_runner.js +199 -157
- package/.agent/scripts/marathon_harness.js +819 -661
- package/.agent/scripts/minify_context.js +115 -100
- package/.agent/scripts/mutation_runner.js +321 -280
- package/.agent/scripts/prompt_compiler.js +62 -42
- package/.agent/scripts/schema_validator.js +373 -280
- package/.agent/scripts/security_scan.js +333 -190
- package/.agent/scripts/session_manager.js +306 -270
- package/.agent/scripts/skill_evolution.js +810 -637
- package/.agent/scripts/skill_integrator.js +327 -307
- package/.agent/scripts/strengthen_skills.js +203 -193
- package/.agent/scripts/swarm_dispatcher.js +558 -457
- package/.agent/scripts/test_runner.js +178 -152
- package/.agent/scripts/verify_all.js +200 -168
- package/.agent/skills/fabel-protocol/SKILL.md +235 -0
- package/.agent/skills/thinking-protocol/SKILL.md +27 -0
- package/.agent/workflows/generate.md +1 -1
- package/.agent/workflows/tribunal-speed.md +1 -1
- package/README.md +53 -53
- package/bin/mcp-server.js +460 -175
- package/bin/tribunal-kit.js +1245 -987
- package/bin/wrapper.js +104 -74
- package/dist/cli.js +31 -0
- package/dist/commands/case.js +23 -0
- package/dist/commands/compile.js +84 -0
- package/dist/commands/init.js +42 -0
- package/dist/commands/learn.js +57 -0
- package/dist/commands/memory.js +456 -0
- package/package.json +2 -2
- package/scripts/benchmark.js +162 -125
- package/scripts/changelog.js +196 -168
- package/scripts/sync-version.js +94 -81
- package/scripts/validate-payload.js +85 -78
|
@@ -19,27 +19,45 @@
|
|
|
19
19
|
* node .agent/scripts/marathon_harness.js add-feature "category" "description" "step1" "step2" ...
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
"use strict";
|
|
23
23
|
|
|
24
|
-
const fs = require(
|
|
25
|
-
const path = require(
|
|
26
|
-
const { execSync } = require(
|
|
24
|
+
const fs = require("fs");
|
|
25
|
+
const path = require("path");
|
|
26
|
+
const { execSync } = require("child_process");
|
|
27
27
|
|
|
28
28
|
const {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
GREEN,
|
|
30
|
+
YELLOW,
|
|
31
|
+
CYAN,
|
|
32
|
+
RED,
|
|
33
|
+
MAGENTA,
|
|
34
|
+
BOLD,
|
|
35
|
+
DIM,
|
|
36
|
+
RESET,
|
|
37
|
+
BOX,
|
|
38
|
+
banner,
|
|
39
|
+
ok,
|
|
40
|
+
warn,
|
|
41
|
+
info,
|
|
42
|
+
} = require("./_colors");
|
|
33
43
|
|
|
34
44
|
// ── Paths ────────────────────────────────────────────────────────────────────
|
|
35
|
-
const MARATHON_DIR = path.resolve(
|
|
36
|
-
const FEATURE_LIST_FILE = path.join(MARATHON_DIR,
|
|
37
|
-
const PROGRESS_FILE = path.join(MARATHON_DIR,
|
|
38
|
-
const ARCHIVE_DIR = path.join(MARATHON_DIR,
|
|
45
|
+
const MARATHON_DIR = path.resolve(".agent", "history", "marathon");
|
|
46
|
+
const FEATURE_LIST_FILE = path.join(MARATHON_DIR, "feature_list.json");
|
|
47
|
+
const PROGRESS_FILE = path.join(MARATHON_DIR, "progress.json");
|
|
48
|
+
const ARCHIVE_DIR = path.join(MARATHON_DIR, "archive");
|
|
39
49
|
|
|
40
50
|
const VALID_COMMANDS = new Set([
|
|
41
|
-
|
|
42
|
-
|
|
51
|
+
"init",
|
|
52
|
+
"status",
|
|
53
|
+
"next",
|
|
54
|
+
"mark",
|
|
55
|
+
"log",
|
|
56
|
+
"session-start",
|
|
57
|
+
"session-end",
|
|
58
|
+
"reset",
|
|
59
|
+
"add-feature",
|
|
60
|
+
"distill",
|
|
43
61
|
]);
|
|
44
62
|
|
|
45
63
|
// ── Schema Defaults ──────────────────────────────────────────────────────────
|
|
@@ -50,12 +68,12 @@ const VALID_COMMANDS = new Set([
|
|
|
50
68
|
* @returns {object}
|
|
51
69
|
*/
|
|
52
70
|
function createFeatureList(spec) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
return {
|
|
72
|
+
spec,
|
|
73
|
+
createdAt: new Date().toISOString(),
|
|
74
|
+
totalFeatures: 0,
|
|
75
|
+
features: [],
|
|
76
|
+
};
|
|
59
77
|
}
|
|
60
78
|
|
|
61
79
|
/**
|
|
@@ -64,13 +82,13 @@ function createFeatureList(spec) {
|
|
|
64
82
|
* @returns {object}
|
|
65
83
|
*/
|
|
66
84
|
function createProgress(spec) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
85
|
+
return {
|
|
86
|
+
spec,
|
|
87
|
+
startedAt: new Date().toISOString(),
|
|
88
|
+
totalSessions: 0,
|
|
89
|
+
sessions: [],
|
|
90
|
+
log: [],
|
|
91
|
+
};
|
|
74
92
|
}
|
|
75
93
|
|
|
76
94
|
// ── File I/O ─────────────────────────────────────────────────────────────────
|
|
@@ -81,14 +99,16 @@ function createProgress(spec) {
|
|
|
81
99
|
* @returns {object|null}
|
|
82
100
|
*/
|
|
83
101
|
function readJSON(filePath) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
102
|
+
if (!fs.existsSync(filePath)) return null;
|
|
103
|
+
try {
|
|
104
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
105
|
+
return JSON.parse(content);
|
|
106
|
+
} catch (e) {
|
|
107
|
+
console.error(
|
|
108
|
+
`${RED}Error reading ${path.basename(filePath)}: ${e.message}${RESET}`,
|
|
109
|
+
);
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
92
112
|
}
|
|
93
113
|
|
|
94
114
|
/**
|
|
@@ -97,15 +117,15 @@ function readJSON(filePath) {
|
|
|
97
117
|
* @param {object} data
|
|
98
118
|
*/
|
|
99
119
|
function writeJSON(filePath, data) {
|
|
100
|
-
|
|
101
|
-
|
|
120
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
121
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8");
|
|
102
122
|
}
|
|
103
123
|
|
|
104
124
|
/**
|
|
105
125
|
* Ensure the marathon directory exists.
|
|
106
126
|
*/
|
|
107
127
|
function ensureDir() {
|
|
108
|
-
|
|
128
|
+
fs.mkdirSync(MARATHON_DIR, { recursive: true });
|
|
109
129
|
}
|
|
110
130
|
|
|
111
131
|
/**
|
|
@@ -113,7 +133,7 @@ function ensureDir() {
|
|
|
113
133
|
* @returns {boolean}
|
|
114
134
|
*/
|
|
115
135
|
function isActive() {
|
|
116
|
-
|
|
136
|
+
return fs.existsSync(FEATURE_LIST_FILE) && fs.existsSync(PROGRESS_FILE);
|
|
117
137
|
}
|
|
118
138
|
|
|
119
139
|
// ── Git Helpers ──────────────────────────────────────────────────────────────
|
|
@@ -124,15 +144,15 @@ function isActive() {
|
|
|
124
144
|
* @returns {string[]}
|
|
125
145
|
*/
|
|
126
146
|
function getGitLog(count = 20) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
147
|
+
try {
|
|
148
|
+
const output = execSync(`git log --oneline -${count}`, {
|
|
149
|
+
encoding: "utf8",
|
|
150
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
151
|
+
});
|
|
152
|
+
return output.trim().split("\n").filter(Boolean);
|
|
153
|
+
} catch {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
136
156
|
}
|
|
137
157
|
|
|
138
158
|
/**
|
|
@@ -140,14 +160,14 @@ function getGitLog(count = 20) {
|
|
|
140
160
|
* @returns {string}
|
|
141
161
|
*/
|
|
142
162
|
function getGitBranch() {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
163
|
+
try {
|
|
164
|
+
return execSync("git branch --show-current", {
|
|
165
|
+
encoding: "utf8",
|
|
166
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
167
|
+
}).trim();
|
|
168
|
+
} catch {
|
|
169
|
+
return "unknown";
|
|
170
|
+
}
|
|
151
171
|
}
|
|
152
172
|
|
|
153
173
|
// ── Progress Helpers ─────────────────────────────────────────────────────────
|
|
@@ -158,22 +178,22 @@ function getGitBranch() {
|
|
|
158
178
|
* @returns {{ total: number, passing: number, failing: number, blocked: number }}
|
|
159
179
|
*/
|
|
160
180
|
function countFeatures(featureList) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
181
|
+
const features = featureList.features || [];
|
|
182
|
+
const total = features.length;
|
|
183
|
+
const passing = features.filter((f) => f.passes === true).length;
|
|
184
|
+
let blocked = 0;
|
|
185
|
+
|
|
186
|
+
features.forEach((f) => {
|
|
187
|
+
if (!f.passes && f.dependencies && f.dependencies.length > 0) {
|
|
188
|
+
const allPassed = f.dependencies.every((depId) => {
|
|
189
|
+
const dep = features.find((d) => d.id === depId);
|
|
190
|
+
return dep && dep.passes === true;
|
|
191
|
+
});
|
|
192
|
+
if (!allPassed) blocked++;
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return { total, passing, failing: total - passing, blocked };
|
|
177
197
|
}
|
|
178
198
|
|
|
179
199
|
/**
|
|
@@ -182,21 +202,23 @@ function countFeatures(featureList) {
|
|
|
182
202
|
* @returns {object|null}
|
|
183
203
|
*/
|
|
184
204
|
function getNextFeature(featureList) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
205
|
+
const features = featureList.features || [];
|
|
206
|
+
return (
|
|
207
|
+
features.find((f) => {
|
|
208
|
+
if (f.passes === true) return false;
|
|
209
|
+
|
|
210
|
+
// Check dependencies (DAG)
|
|
211
|
+
if (f.dependencies && f.dependencies.length > 0) {
|
|
212
|
+
const allPassed = f.dependencies.every((depId) => {
|
|
213
|
+
const dep = features.find((d) => d.id === depId);
|
|
214
|
+
return dep && dep.passes === true;
|
|
215
|
+
});
|
|
216
|
+
if (!allPassed) return false; // Feature is blocked
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return true;
|
|
220
|
+
}) || null
|
|
221
|
+
);
|
|
200
222
|
}
|
|
201
223
|
|
|
202
224
|
/**
|
|
@@ -207,17 +229,17 @@ function getNextFeature(featureList) {
|
|
|
207
229
|
* @returns {string}
|
|
208
230
|
*/
|
|
209
231
|
function progressBar(current, total, width = 30) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
232
|
+
if (total === 0) return `${DIM}[${"░".repeat(width)}]${RESET} 0%`;
|
|
233
|
+
const pct = Math.round((current / total) * 100);
|
|
234
|
+
const filled = Math.round((current / total) * width);
|
|
235
|
+
const empty = width - filled;
|
|
214
236
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
237
|
+
let color = RED;
|
|
238
|
+
if (pct >= 75) color = GREEN;
|
|
239
|
+
else if (pct >= 40) color = YELLOW;
|
|
240
|
+
else if (pct >= 15) color = CYAN;
|
|
219
241
|
|
|
220
|
-
|
|
242
|
+
return `${color}[${"█".repeat(filled)}${"░".repeat(empty)}]${RESET} ${BOLD}${pct}%${RESET}`;
|
|
221
243
|
}
|
|
222
244
|
|
|
223
245
|
// ── Commands ─────────────────────────────────────────────────────────────────
|
|
@@ -227,37 +249,49 @@ function progressBar(current, total, width = 30) {
|
|
|
227
249
|
* @param {string} spec
|
|
228
250
|
*/
|
|
229
251
|
function cmdInit(spec) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
252
|
+
if (isActive()) {
|
|
253
|
+
console.error(`${RED}❌ A marathon is already active.${RESET}`);
|
|
254
|
+
console.error(
|
|
255
|
+
` Use ${CYAN}reset${RESET} to archive it first, or ${CYAN}status${RESET} to view progress.`,
|
|
256
|
+
);
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (!spec) {
|
|
261
|
+
console.error(
|
|
262
|
+
`${RED}❌ Spec required. Usage: marathon_harness.js init "Build a todo app"${RESET}`,
|
|
263
|
+
);
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
ensureDir();
|
|
268
|
+
|
|
269
|
+
const featureList = createFeatureList(spec);
|
|
270
|
+
const progress = createProgress(spec);
|
|
271
|
+
|
|
272
|
+
writeJSON(FEATURE_LIST_FILE, featureList);
|
|
273
|
+
writeJSON(PROGRESS_FILE, progress);
|
|
274
|
+
|
|
275
|
+
console.log(banner("marathon_harness.js", { Mode: "INIT" }));
|
|
276
|
+
console.log();
|
|
277
|
+
ok(`Marathon initialized for: ${BOLD}${spec}${RESET}`);
|
|
278
|
+
console.log();
|
|
279
|
+
info("Next steps for the agent:");
|
|
280
|
+
console.log(
|
|
281
|
+
` ${DIM}1.${RESET} Decompose the spec into 30-200 atomic features`,
|
|
282
|
+
);
|
|
283
|
+
console.log(
|
|
284
|
+
` ${DIM}2.${RESET} Add each feature with: ${CYAN}add-feature "category" "description" "step1" "step2" ...${RESET}`,
|
|
285
|
+
);
|
|
286
|
+
console.log(
|
|
287
|
+
` ${DIM}3.${RESET} Make an initial git commit: ${CYAN}git commit -m "marathon: initial scaffold"${RESET}`,
|
|
288
|
+
);
|
|
289
|
+
console.log(
|
|
290
|
+
` ${DIM}4.${RESET} Start the first session: ${CYAN}session-start${RESET}`,
|
|
291
|
+
);
|
|
292
|
+
console.log();
|
|
293
|
+
console.log(` ${DIM}State directory: ${MARATHON_DIR}${RESET}`);
|
|
294
|
+
console.log();
|
|
261
295
|
}
|
|
262
296
|
|
|
263
297
|
/**
|
|
@@ -268,183 +302,224 @@ function cmdInit(spec) {
|
|
|
268
302
|
* @param {number[]} deps
|
|
269
303
|
*/
|
|
270
304
|
function cmdAddFeature(category, description, steps, deps = []) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
305
|
+
if (!isActive()) {
|
|
306
|
+
console.error(
|
|
307
|
+
`${RED}❌ No active marathon. Run ${CYAN}init${RED} first.${RESET}`,
|
|
308
|
+
);
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (!category || !description) {
|
|
313
|
+
console.error(
|
|
314
|
+
`${RED}❌ Usage: add-feature "category" "description" "step1" "step2" ...${RESET}`,
|
|
315
|
+
);
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const featureList = readJSON(FEATURE_LIST_FILE);
|
|
320
|
+
if (!featureList) process.exit(1);
|
|
321
|
+
|
|
322
|
+
const newId =
|
|
323
|
+
featureList.features.length > 0
|
|
324
|
+
? Math.max(...featureList.features.map((f) => f.id)) + 1
|
|
325
|
+
: 1;
|
|
326
|
+
|
|
327
|
+
const feature = {
|
|
328
|
+
id: newId,
|
|
329
|
+
category: category.toLowerCase(),
|
|
330
|
+
description,
|
|
331
|
+
steps: steps.length > 0 ? steps : ["Implement and verify"],
|
|
332
|
+
dependencies: deps,
|
|
333
|
+
attempts: 0,
|
|
334
|
+
failureReasons: [],
|
|
335
|
+
passes: false,
|
|
336
|
+
sessionCompleted: null,
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
featureList.features.push(feature);
|
|
340
|
+
featureList.totalFeatures = featureList.features.length;
|
|
341
|
+
|
|
342
|
+
writeJSON(FEATURE_LIST_FILE, featureList);
|
|
343
|
+
|
|
344
|
+
console.log(
|
|
345
|
+
` ${GREEN}+${RESET} Feature ${BOLD}#${newId}${RESET} [${MAGENTA}${category}${RESET}]: ${description}`,
|
|
346
|
+
);
|
|
306
347
|
}
|
|
307
348
|
|
|
308
349
|
/**
|
|
309
350
|
* Show the marathon status dashboard.
|
|
310
351
|
*/
|
|
311
352
|
function cmdStatus() {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
353
|
+
if (!isActive()) {
|
|
354
|
+
console.log(
|
|
355
|
+
`${YELLOW}No active marathon.${RESET} Start one with: ${CYAN}marathon_harness.js init "spec"${RESET}`,
|
|
356
|
+
);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const featureList = readJSON(FEATURE_LIST_FILE);
|
|
361
|
+
const progress = readJSON(PROGRESS_FILE);
|
|
362
|
+
if (!featureList || !progress) return;
|
|
363
|
+
|
|
364
|
+
const { total, passing, blocked } = countFeatures(featureList);
|
|
365
|
+
const nextFeature = getNextFeature(featureList);
|
|
366
|
+
const sessions = progress.sessions || [];
|
|
367
|
+
const lastSession = sessions[sessions.length - 1] || null;
|
|
368
|
+
|
|
369
|
+
console.log(banner("marathon_harness.js", { Mode: "STATUS" }));
|
|
370
|
+
console.log();
|
|
371
|
+
|
|
372
|
+
// ── Spec ──
|
|
373
|
+
console.log(` ${BOLD}Spec:${RESET} ${featureList.spec}`);
|
|
374
|
+
console.log(` ${DIM}Started: ${featureList.createdAt.slice(0, 16)}${RESET}`);
|
|
375
|
+
console.log();
|
|
376
|
+
|
|
377
|
+
// ── Progress Bar ──
|
|
378
|
+
const blockedInfo =
|
|
379
|
+
blocked > 0 ? ` (${YELLOW}${blocked} blocked${RESET})` : "";
|
|
380
|
+
console.log(
|
|
381
|
+
` ${BOLD}Progress:${RESET} ${progressBar(passing, total)} ${GREEN}${passing}${RESET}/${total} features${blockedInfo}`,
|
|
382
|
+
);
|
|
383
|
+
console.log();
|
|
384
|
+
|
|
385
|
+
// ── Category Breakdown ──
|
|
386
|
+
const categories = {};
|
|
387
|
+
for (const f of featureList.features) {
|
|
388
|
+
const cat = f.category || "uncategorized";
|
|
389
|
+
if (!categories[cat]) categories[cat] = { total: 0, passing: 0 };
|
|
390
|
+
categories[cat].total++;
|
|
391
|
+
if (f.passes) categories[cat].passing++;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (Object.keys(categories).length > 0) {
|
|
395
|
+
console.log(` ${BOLD}By Category:${RESET}`);
|
|
396
|
+
for (const [cat, counts] of Object.entries(categories)) {
|
|
397
|
+
const catPct =
|
|
398
|
+
counts.total > 0
|
|
399
|
+
? Math.round((counts.passing / counts.total) * 100)
|
|
400
|
+
: 0;
|
|
401
|
+
const catColor = catPct === 100 ? GREEN : catPct >= 50 ? YELLOW : RED;
|
|
402
|
+
console.log(
|
|
403
|
+
` ${MAGENTA}${cat.padEnd(18)}${RESET} ${catColor}${counts.passing}/${counts.total}${RESET} (${catPct}%)`,
|
|
404
|
+
);
|
|
315
405
|
}
|
|
316
|
-
|
|
317
|
-
const featureList = readJSON(FEATURE_LIST_FILE);
|
|
318
|
-
const progress = readJSON(PROGRESS_FILE);
|
|
319
|
-
if (!featureList || !progress) return;
|
|
320
|
-
|
|
321
|
-
const { total, passing, failing, blocked } = countFeatures(featureList);
|
|
322
|
-
const nextFeature = getNextFeature(featureList);
|
|
323
|
-
const sessions = progress.sessions || [];
|
|
324
|
-
const lastSession = sessions[sessions.length - 1] || null;
|
|
325
|
-
|
|
326
|
-
console.log(banner('marathon_harness.js', { Mode: 'STATUS' }));
|
|
327
406
|
console.log();
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
// ── Category Breakdown ──
|
|
340
|
-
const categories = {};
|
|
341
|
-
for (const f of featureList.features) {
|
|
342
|
-
const cat = f.category || 'uncategorized';
|
|
343
|
-
if (!categories[cat]) categories[cat] = { total: 0, passing: 0 };
|
|
344
|
-
categories[cat].total++;
|
|
345
|
-
if (f.passes) categories[cat].passing++;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ── Sessions ──
|
|
410
|
+
console.log(` ${BOLD}Sessions:${RESET} ${sessions.length} completed`);
|
|
411
|
+
if (lastSession) {
|
|
412
|
+
console.log(
|
|
413
|
+
` ${DIM}Last session:${RESET} #${lastSession.session} — ${lastSession.endedAt?.slice(0, 16) || "in progress"}`,
|
|
414
|
+
);
|
|
415
|
+
if (lastSession.notes) {
|
|
416
|
+
console.log(` ${DIM}Notes:${RESET} ${lastSession.notes.slice(0, 80)}`);
|
|
346
417
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
355
|
-
console.log();
|
|
418
|
+
if (lastSession.featuresAtEnd) {
|
|
419
|
+
const delta =
|
|
420
|
+
lastSession.featuresAtEnd.passing -
|
|
421
|
+
(lastSession.featuresAtStart?.passing || 0);
|
|
422
|
+
console.log(
|
|
423
|
+
` ${DIM}Features completed:${RESET} ${GREEN}+${delta}${RESET}`,
|
|
424
|
+
);
|
|
356
425
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
426
|
+
}
|
|
427
|
+
console.log();
|
|
428
|
+
|
|
429
|
+
// ── Next Feature ──
|
|
430
|
+
if (nextFeature) {
|
|
431
|
+
console.log(
|
|
432
|
+
` ${BOLD}Next Feature:${RESET} ${CYAN}#${nextFeature.id}${RESET} [${MAGENTA}${nextFeature.category}${RESET}]`,
|
|
433
|
+
);
|
|
434
|
+
console.log(` ${nextFeature.description}`);
|
|
435
|
+
if (nextFeature.steps && nextFeature.steps.length > 0) {
|
|
436
|
+
console.log(` ${DIM}Steps:${RESET}`);
|
|
437
|
+
for (const step of nextFeature.steps) {
|
|
438
|
+
console.log(` ${DIM}${BOX.bulletEmpty}${RESET} ${step}`);
|
|
439
|
+
}
|
|
369
440
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
441
|
+
} else if (total > 0) {
|
|
442
|
+
console.log(
|
|
443
|
+
` ${GREEN}${BOLD}🎉 All ${total} features are passing!${RESET}`,
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
console.log();
|
|
447
|
+
|
|
448
|
+
// ── Git ──
|
|
449
|
+
const branch = getGitBranch();
|
|
450
|
+
const recentCommits = getGitLog(5);
|
|
451
|
+
if (recentCommits.length > 0) {
|
|
452
|
+
console.log(` ${BOLD}Git:${RESET} ${DIM}branch: ${branch}${RESET}`);
|
|
453
|
+
for (const commit of recentCommits.slice(0, 3)) {
|
|
454
|
+
console.log(` ${DIM}${commit}${RESET}`);
|
|
384
455
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
// ── Git ──
|
|
388
|
-
const branch = getGitBranch();
|
|
389
|
-
const recentCommits = getGitLog(5);
|
|
390
|
-
if (recentCommits.length > 0) {
|
|
391
|
-
console.log(` ${BOLD}Git:${RESET} ${DIM}branch: ${branch}${RESET}`);
|
|
392
|
-
for (const commit of recentCommits.slice(0, 3)) {
|
|
393
|
-
console.log(` ${DIM}${commit}${RESET}`);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
console.log();
|
|
456
|
+
}
|
|
457
|
+
console.log();
|
|
397
458
|
}
|
|
398
459
|
|
|
399
460
|
/**
|
|
400
461
|
* Show the next unfinished feature.
|
|
401
462
|
*/
|
|
402
463
|
function cmdNext() {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
464
|
+
if (!isActive()) {
|
|
465
|
+
console.error(`${RED}❌ No active marathon.${RESET}`);
|
|
466
|
+
process.exit(1);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const featureList = readJSON(FEATURE_LIST_FILE);
|
|
470
|
+
if (!featureList) process.exit(1);
|
|
471
|
+
|
|
472
|
+
const { total, passing } = countFeatures(featureList);
|
|
473
|
+
const nextFeature = getNextFeature(featureList);
|
|
474
|
+
|
|
475
|
+
if (!nextFeature) {
|
|
476
|
+
if (passing === total) {
|
|
477
|
+
console.log(
|
|
478
|
+
`${GREEN}${BOLD}🎉 All ${total} features are passing! Marathon complete.${RESET}`,
|
|
479
|
+
);
|
|
480
|
+
} else {
|
|
481
|
+
console.log(
|
|
482
|
+
`${RED}${BOLD}⚠️ Deadlock detected: ${total - passing} features remain, but all are blocked by failing dependencies.${RESET}`,
|
|
483
|
+
);
|
|
484
|
+
console.log(
|
|
485
|
+
` ${DIM}Check 'status' and use 'mark <id> pass' to resolve dependencies.${RESET}`,
|
|
486
|
+
);
|
|
406
487
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
console.log(
|
|
492
|
+
`\n ${BOLD}Progress:${RESET} ${progressBar(passing, total)} ${GREEN}${passing}${RESET}/${total}`,
|
|
493
|
+
);
|
|
494
|
+
console.log();
|
|
495
|
+
console.log(
|
|
496
|
+
` ${BOLD}Next Feature:${RESET} ${CYAN}#${nextFeature.id}${RESET} [${MAGENTA}${nextFeature.category}${RESET}]`,
|
|
497
|
+
);
|
|
498
|
+
console.log(` ${nextFeature.description}`);
|
|
499
|
+
console.log();
|
|
500
|
+
|
|
501
|
+
if (nextFeature.steps && nextFeature.steps.length > 0) {
|
|
502
|
+
console.log(` ${BOLD}Steps:${RESET}`);
|
|
503
|
+
for (const step of nextFeature.steps) {
|
|
504
|
+
console.log(` ${BOX.bulletEmpty} ${step}`);
|
|
422
505
|
}
|
|
423
|
-
|
|
424
|
-
console.log(`\n ${BOLD}Progress:${RESET} ${progressBar(passing, total)} ${GREEN}${passing}${RESET}/${total}`);
|
|
425
506
|
console.log();
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
}
|
|
435
|
-
console.log();
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
if (nextFeature.failureReasons && nextFeature.failureReasons.length > 0) {
|
|
439
|
-
console.log(` ${RED}${BOLD}Previous Failures (${nextFeature.attempts} attempts):${RESET}`);
|
|
440
|
-
for (const reason of nextFeature.failureReasons) {
|
|
441
|
-
console.log(` ${DIM}* ${reason}${RESET}`);
|
|
442
|
-
}
|
|
443
|
-
console.log();
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (nextFeature.failureReasons && nextFeature.failureReasons.length > 0) {
|
|
510
|
+
console.log(
|
|
511
|
+
` ${RED}${BOLD}Previous Failures (${nextFeature.attempts} attempts):${RESET}`,
|
|
512
|
+
);
|
|
513
|
+
for (const reason of nextFeature.failureReasons) {
|
|
514
|
+
console.log(` ${DIM}* ${reason}${RESET}`);
|
|
444
515
|
}
|
|
445
|
-
|
|
446
|
-
console.log(` ${DIM}When done: marathon_harness.js mark ${nextFeature.id} pass${RESET}`);
|
|
447
516
|
console.log();
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
console.log(
|
|
520
|
+
` ${DIM}When done: marathon_harness.js mark ${nextFeature.id} pass${RESET}`,
|
|
521
|
+
);
|
|
522
|
+
console.log();
|
|
448
523
|
}
|
|
449
524
|
|
|
450
525
|
/**
|
|
@@ -454,56 +529,64 @@ function cmdNext() {
|
|
|
454
529
|
* @param {string} [reason] - Reason for failure
|
|
455
530
|
*/
|
|
456
531
|
function cmdMark(id, verdict, reason) {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
532
|
+
if (!isActive()) {
|
|
533
|
+
console.error(`${RED}❌ No active marathon.${RESET}`);
|
|
534
|
+
process.exit(1);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const validVerdicts = ["pass", "fail"];
|
|
538
|
+
if (!validVerdicts.includes(verdict)) {
|
|
539
|
+
console.error(
|
|
540
|
+
`${RED}❌ Invalid verdict "${verdict}". Use: pass | fail${RESET}`,
|
|
541
|
+
);
|
|
542
|
+
process.exit(1);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const featureList = readJSON(FEATURE_LIST_FILE);
|
|
546
|
+
if (!featureList) process.exit(1);
|
|
547
|
+
|
|
548
|
+
const feature = featureList.features.find((f) => f.id === id);
|
|
549
|
+
if (!feature) {
|
|
550
|
+
console.error(
|
|
551
|
+
`${RED}❌ Feature #${id} not found. Valid IDs: 1-${featureList.features.length}${RESET}`,
|
|
552
|
+
);
|
|
553
|
+
process.exit(1);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const newPasses = verdict === "pass";
|
|
557
|
+
const oldPasses = feature.passes;
|
|
558
|
+
|
|
559
|
+
// Guard: don't allow editing description or steps
|
|
560
|
+
feature.passes = newPasses;
|
|
561
|
+
feature.sessionCompleted = newPasses ? new Date().toISOString() : null;
|
|
562
|
+
|
|
563
|
+
if (!newPasses) {
|
|
564
|
+
feature.attempts = (feature.attempts || 0) + 1;
|
|
565
|
+
if (reason) {
|
|
566
|
+
if (!feature.failureReasons) feature.failureReasons = [];
|
|
567
|
+
feature.failureReasons.push(`Attempt ${feature.attempts}: ${reason}`);
|
|
466
568
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
feature.failureReasons.push(`Attempt ${feature.attempts}: ${reason}`);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
writeJSON(FEATURE_LIST_FILE, featureList);
|
|
493
|
-
|
|
494
|
-
const { total, passing } = countFeatures(featureList);
|
|
495
|
-
|
|
496
|
-
if (newPasses && !oldPasses) {
|
|
497
|
-
ok(`Feature #${id} marked as ${GREEN}PASSING${RESET}`);
|
|
498
|
-
} else if (!newPasses && oldPasses) {
|
|
499
|
-
warn(`Feature #${id} marked as ${RED}FAILING${RESET}`);
|
|
500
|
-
} else {
|
|
501
|
-
info(`Feature #${id} unchanged (already ${newPasses ? 'passing' : 'failing'})`);
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
console.log(` ${DIM}${feature.description}${RESET}`);
|
|
505
|
-
console.log(` ${progressBar(passing, total)} ${GREEN}${passing}${RESET}/${total}`);
|
|
506
|
-
console.log();
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
writeJSON(FEATURE_LIST_FILE, featureList);
|
|
572
|
+
|
|
573
|
+
const { total, passing } = countFeatures(featureList);
|
|
574
|
+
|
|
575
|
+
if (newPasses && !oldPasses) {
|
|
576
|
+
ok(`Feature #${id} marked as ${GREEN}PASSING${RESET}`);
|
|
577
|
+
} else if (!newPasses && oldPasses) {
|
|
578
|
+
warn(`Feature #${id} marked as ${RED}FAILING${RESET}`);
|
|
579
|
+
} else {
|
|
580
|
+
info(
|
|
581
|
+
`Feature #${id} unchanged (already ${newPasses ? "passing" : "failing"})`,
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
console.log(` ${DIM}${feature.description}${RESET}`);
|
|
586
|
+
console.log(
|
|
587
|
+
` ${progressBar(passing, total)} ${GREEN}${passing}${RESET}/${total}`,
|
|
588
|
+
);
|
|
589
|
+
console.log();
|
|
507
590
|
}
|
|
508
591
|
|
|
509
592
|
/**
|
|
@@ -511,27 +594,29 @@ function cmdMark(id, verdict, reason) {
|
|
|
511
594
|
* @param {string} message
|
|
512
595
|
*/
|
|
513
596
|
function cmdLog(message) {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
597
|
+
if (!isActive()) {
|
|
598
|
+
console.error(`${RED}❌ No active marathon.${RESET}`);
|
|
599
|
+
process.exit(1);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (!message) {
|
|
603
|
+
console.error(
|
|
604
|
+
`${RED}❌ Message required. Usage: log "Your progress note"${RESET}`,
|
|
605
|
+
);
|
|
606
|
+
process.exit(1);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const progress = readJSON(PROGRESS_FILE);
|
|
610
|
+
if (!progress) process.exit(1);
|
|
611
|
+
|
|
612
|
+
if (!progress.log) progress.log = [];
|
|
613
|
+
progress.log.push({
|
|
614
|
+
timestamp: new Date().toISOString(),
|
|
615
|
+
message,
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
writeJSON(PROGRESS_FILE, progress);
|
|
619
|
+
ok(`Logged: ${message}`);
|
|
535
620
|
}
|
|
536
621
|
|
|
537
622
|
/**
|
|
@@ -539,125 +624,145 @@ function cmdLog(message) {
|
|
|
539
624
|
* @param {string} lesson
|
|
540
625
|
*/
|
|
541
626
|
function cmdDistill(lesson) {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
627
|
+
if (!isActive()) {
|
|
628
|
+
console.error(`${RED}❌ No active marathon.${RESET}`);
|
|
629
|
+
process.exit(1);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (!lesson) {
|
|
633
|
+
console.error(
|
|
634
|
+
`${RED}❌ Lesson required. Usage: distill "Your architectural lesson"${RESET}`,
|
|
635
|
+
);
|
|
636
|
+
process.exit(1);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
ensureDir();
|
|
640
|
+
const DISTILL_FILE = path.join(MARATHON_DIR, "distilled_context.md");
|
|
641
|
+
const timestamp = new Date().toISOString().slice(0, 16);
|
|
642
|
+
const entry = `- [${timestamp}] ${lesson}\n`;
|
|
643
|
+
|
|
644
|
+
fs.appendFileSync(DISTILL_FILE, entry, "utf8");
|
|
645
|
+
ok(`Distilled memory saved: ${lesson}`);
|
|
559
646
|
}
|
|
560
647
|
|
|
561
648
|
/**
|
|
562
649
|
* Start a new session — reads state, shows bearings.
|
|
563
650
|
*/
|
|
564
651
|
function cmdSessionStart() {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
652
|
+
if (!isActive()) {
|
|
653
|
+
console.error(`${RED}❌ No active marathon.${RESET}`);
|
|
654
|
+
process.exit(1);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const featureList = readJSON(FEATURE_LIST_FILE);
|
|
658
|
+
const progress = readJSON(PROGRESS_FILE);
|
|
659
|
+
if (!featureList || !progress) process.exit(1);
|
|
660
|
+
|
|
661
|
+
const sessionNum = progress.sessions.length + 1;
|
|
662
|
+
const { total, passing } = countFeatures(featureList);
|
|
663
|
+
const nextFeature = getNextFeature(featureList);
|
|
664
|
+
|
|
665
|
+
// Record session start
|
|
666
|
+
const session = {
|
|
667
|
+
session: sessionNum,
|
|
668
|
+
startedAt: new Date().toISOString(),
|
|
669
|
+
endedAt: null,
|
|
670
|
+
featuresAtStart: { total, passing },
|
|
671
|
+
featuresAtEnd: null,
|
|
672
|
+
featuresCompleted: [],
|
|
673
|
+
notes: null,
|
|
674
|
+
gitCommits: [],
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
progress.sessions.push(session);
|
|
678
|
+
progress.totalSessions = progress.sessions.length;
|
|
679
|
+
writeJSON(PROGRESS_FILE, progress);
|
|
680
|
+
|
|
681
|
+
// Display bearings
|
|
682
|
+
console.log(
|
|
683
|
+
banner("marathon_harness.js", {
|
|
684
|
+
Mode: "SESSION START",
|
|
685
|
+
Session: `#${sessionNum}`,
|
|
686
|
+
}),
|
|
687
|
+
);
|
|
688
|
+
console.log();
|
|
689
|
+
|
|
690
|
+
// ── Spec ──
|
|
691
|
+
console.log(` ${BOLD}Spec:${RESET} ${featureList.spec}`);
|
|
692
|
+
console.log(
|
|
693
|
+
` ${BOLD}Progress:${RESET} ${progressBar(passing, total)} ${GREEN}${passing}${RESET}/${total}`,
|
|
694
|
+
);
|
|
695
|
+
console.log();
|
|
696
|
+
|
|
697
|
+
// ── Recent git commits ──
|
|
698
|
+
const commits = getGitLog(10);
|
|
699
|
+
if (commits.length > 0) {
|
|
700
|
+
console.log(` ${BOLD}Recent Commits:${RESET}`);
|
|
701
|
+
for (const commit of commits.slice(0, 5)) {
|
|
702
|
+
console.log(` ${DIM}${commit}${RESET}`);
|
|
568
703
|
}
|
|
569
|
-
|
|
570
|
-
const featureList = readJSON(FEATURE_LIST_FILE);
|
|
571
|
-
const progress = readJSON(PROGRESS_FILE);
|
|
572
|
-
if (!featureList || !progress) process.exit(1);
|
|
573
|
-
|
|
574
|
-
const sessionNum = (progress.sessions.length) + 1;
|
|
575
|
-
const { total, passing } = countFeatures(featureList);
|
|
576
|
-
const nextFeature = getNextFeature(featureList);
|
|
577
|
-
|
|
578
|
-
// Record session start
|
|
579
|
-
const session = {
|
|
580
|
-
session: sessionNum,
|
|
581
|
-
startedAt: new Date().toISOString(),
|
|
582
|
-
endedAt: null,
|
|
583
|
-
featuresAtStart: { total, passing },
|
|
584
|
-
featuresAtEnd: null,
|
|
585
|
-
featuresCompleted: [],
|
|
586
|
-
notes: null,
|
|
587
|
-
gitCommits: []
|
|
588
|
-
};
|
|
589
|
-
|
|
590
|
-
progress.sessions.push(session);
|
|
591
|
-
progress.totalSessions = progress.sessions.length;
|
|
592
|
-
writeJSON(PROGRESS_FILE, progress);
|
|
593
|
-
|
|
594
|
-
// Display bearings
|
|
595
|
-
console.log(banner('marathon_harness.js', {
|
|
596
|
-
Mode: 'SESSION START',
|
|
597
|
-
Session: `#${sessionNum}`
|
|
598
|
-
}));
|
|
599
704
|
console.log();
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
console.log(` ${BOLD}Recent Commits:${RESET}`);
|
|
610
|
-
for (const commit of commits.slice(0, 5)) {
|
|
611
|
-
console.log(` ${DIM}${commit}${RESET}`);
|
|
612
|
-
}
|
|
613
|
-
console.log();
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// ── Last session notes ──
|
|
708
|
+
if (progress.sessions.length > 1) {
|
|
709
|
+
const prev = progress.sessions[progress.sessions.length - 2];
|
|
710
|
+
if (prev && prev.notes) {
|
|
711
|
+
console.log(` ${BOLD}Last Session Notes:${RESET}`);
|
|
712
|
+
console.log(` ${DIM}${prev.notes}${RESET}`);
|
|
713
|
+
console.log();
|
|
614
714
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// ── Recent log entries ──
|
|
718
|
+
const recentLogs = (progress.log || []).slice(-3);
|
|
719
|
+
if (recentLogs.length > 0) {
|
|
720
|
+
console.log(` ${BOLD}Recent Log:${RESET}`);
|
|
721
|
+
for (const entry of recentLogs) {
|
|
722
|
+
console.log(
|
|
723
|
+
` ${DIM}${entry.timestamp.slice(0, 16)}${RESET} ${entry.message}`,
|
|
724
|
+
);
|
|
624
725
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
726
|
+
console.log();
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// ── Next feature ──
|
|
730
|
+
if (nextFeature) {
|
|
731
|
+
console.log(
|
|
732
|
+
` ${BOLD}${CYAN}▸ Next Feature:${RESET} ${CYAN}#${nextFeature.id}${RESET} [${MAGENTA}${nextFeature.category}${RESET}]`,
|
|
733
|
+
);
|
|
734
|
+
console.log(` ${nextFeature.description}`);
|
|
735
|
+
if (nextFeature.steps && nextFeature.steps.length > 0) {
|
|
736
|
+
for (const step of nextFeature.steps) {
|
|
737
|
+
console.log(` ${DIM}${BOX.bulletEmpty}${RESET} ${step}`);
|
|
738
|
+
}
|
|
634
739
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
if (nextFeature.steps && nextFeature.steps.length > 0) {
|
|
641
|
-
for (const step of nextFeature.steps) {
|
|
642
|
-
console.log(` ${DIM}${BOX.bulletEmpty}${RESET} ${step}`);
|
|
643
|
-
}
|
|
644
|
-
}
|
|
740
|
+
} else {
|
|
741
|
+
if (passing === total) {
|
|
742
|
+
console.log(
|
|
743
|
+
` ${GREEN}${BOLD}🎉 All features passing! Nothing to implement.${RESET}`,
|
|
744
|
+
);
|
|
645
745
|
} else {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
console.log(` ${RED}${BOLD}⚠️ Deadlock: ${total - passing} features are blocked by failing dependencies.${RESET}`);
|
|
650
|
-
}
|
|
746
|
+
console.log(
|
|
747
|
+
` ${RED}${BOLD}⚠️ Deadlock: ${total - passing} features are blocked by failing dependencies.${RESET}`,
|
|
748
|
+
);
|
|
651
749
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
750
|
+
}
|
|
751
|
+
console.log();
|
|
752
|
+
|
|
753
|
+
// ── Recommended actions ──
|
|
754
|
+
console.log(` ${BOLD}Recommended Actions:${RESET}`);
|
|
755
|
+
console.log(
|
|
756
|
+
` ${DIM}1.${RESET} Start dev server (if applicable): ${CYAN}node .agent/scripts/auto_preview.js start${RESET}`,
|
|
757
|
+
);
|
|
758
|
+
console.log(
|
|
759
|
+
` ${DIM}2.${RESET} Smoke test the app to verify it's not broken`,
|
|
760
|
+
);
|
|
761
|
+
console.log(` ${DIM}3.${RESET} Implement the next feature shown above`);
|
|
762
|
+
console.log(
|
|
763
|
+
` ${DIM}4.${RESET} Test, mark as passing, commit, then pick next feature`,
|
|
764
|
+
);
|
|
765
|
+
console.log();
|
|
661
766
|
}
|
|
662
767
|
|
|
663
768
|
/**
|
|
@@ -665,232 +770,285 @@ function cmdSessionStart() {
|
|
|
665
770
|
* @param {string} summary
|
|
666
771
|
*/
|
|
667
772
|
function cmdSessionEnd(summary) {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
console.log();
|
|
773
|
+
if (!isActive()) {
|
|
774
|
+
console.error(`${RED}❌ No active marathon.${RESET}`);
|
|
775
|
+
process.exit(1);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const featureList = readJSON(FEATURE_LIST_FILE);
|
|
779
|
+
const progress = readJSON(PROGRESS_FILE);
|
|
780
|
+
if (!featureList || !progress) process.exit(1);
|
|
781
|
+
|
|
782
|
+
const sessions = progress.sessions || [];
|
|
783
|
+
if (sessions.length === 0) {
|
|
784
|
+
console.error(
|
|
785
|
+
`${RED}❌ No active session. Run ${CYAN}session-start${RED} first.${RESET}`,
|
|
786
|
+
);
|
|
787
|
+
process.exit(1);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const currentSession = sessions[sessions.length - 1];
|
|
791
|
+
const { total, passing } = countFeatures(featureList);
|
|
792
|
+
|
|
793
|
+
// Calculate features completed during this session
|
|
794
|
+
const startPassing = currentSession.featuresAtStart?.passing || 0;
|
|
795
|
+
const completedThisSession = passing - startPassing;
|
|
796
|
+
|
|
797
|
+
// Find which features were completed (have sessionCompleted in this session range)
|
|
798
|
+
const sessionStartTime = currentSession.startedAt;
|
|
799
|
+
const completedIds = featureList.features
|
|
800
|
+
.filter(
|
|
801
|
+
(f) =>
|
|
802
|
+
f.passes &&
|
|
803
|
+
f.sessionCompleted &&
|
|
804
|
+
f.sessionCompleted >= sessionStartTime,
|
|
805
|
+
)
|
|
806
|
+
.map((f) => f.id);
|
|
807
|
+
|
|
808
|
+
// Get git commits since session start
|
|
809
|
+
let sessionCommits = [];
|
|
810
|
+
try {
|
|
811
|
+
const since = currentSession.startedAt;
|
|
812
|
+
const output = execSync(`git log --oneline --since="${since}"`, {
|
|
813
|
+
encoding: "utf8",
|
|
814
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
815
|
+
});
|
|
816
|
+
sessionCommits = output
|
|
817
|
+
.trim()
|
|
818
|
+
.split("\n")
|
|
819
|
+
.filter(Boolean)
|
|
820
|
+
.map((l) => l.split(" ")[0]);
|
|
821
|
+
} catch {
|
|
822
|
+
// Git not available or no commits
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Update session record
|
|
826
|
+
currentSession.endedAt = new Date().toISOString();
|
|
827
|
+
currentSession.featuresAtEnd = { total, passing };
|
|
828
|
+
currentSession.featuresCompleted = completedIds;
|
|
829
|
+
currentSession.notes =
|
|
830
|
+
summary ||
|
|
831
|
+
`Session ${currentSession.session}: ${completedThisSession} features completed`;
|
|
832
|
+
currentSession.gitCommits = sessionCommits;
|
|
833
|
+
|
|
834
|
+
writeJSON(PROGRESS_FILE, progress);
|
|
835
|
+
|
|
836
|
+
// Display summary
|
|
837
|
+
console.log(
|
|
838
|
+
banner("marathon_harness.js", {
|
|
839
|
+
Mode: "SESSION END",
|
|
840
|
+
Session: `#${currentSession.session}`,
|
|
841
|
+
}),
|
|
842
|
+
);
|
|
843
|
+
console.log();
|
|
844
|
+
|
|
845
|
+
console.log(` ${BOLD}Session #${currentSession.session} Summary:${RESET}`);
|
|
846
|
+
console.log(` Started: ${currentSession.startedAt.slice(0, 16)}`);
|
|
847
|
+
console.log(` Ended: ${currentSession.endedAt.slice(0, 16)}`);
|
|
848
|
+
console.log(
|
|
849
|
+
` Features: ${GREEN}+${completedThisSession}${RESET} completed (${completedIds.map((id) => `#${id}`).join(", ") || "none"})`,
|
|
850
|
+
);
|
|
851
|
+
console.log(` Commits: ${sessionCommits.length}`);
|
|
852
|
+
if (summary) {
|
|
853
|
+
console.log(` Notes: ${summary}`);
|
|
854
|
+
}
|
|
855
|
+
console.log();
|
|
856
|
+
|
|
857
|
+
console.log(
|
|
858
|
+
` ${BOLD}Overall Progress:${RESET} ${progressBar(passing, total)} ${GREEN}${passing}${RESET}/${total}`,
|
|
859
|
+
);
|
|
860
|
+
console.log();
|
|
861
|
+
|
|
862
|
+
const remaining = total - passing;
|
|
863
|
+
if (remaining > 0) {
|
|
864
|
+
const avgPerSession =
|
|
865
|
+
sessions.length > 0
|
|
866
|
+
? Math.max(1, Math.round(passing / sessions.length))
|
|
867
|
+
: 1;
|
|
868
|
+
const estRemaining = Math.ceil(remaining / avgPerSession);
|
|
869
|
+
console.log(
|
|
870
|
+
` ${DIM}Estimated sessions remaining: ~${estRemaining} (avg ${avgPerSession} features/session)${RESET}`,
|
|
871
|
+
);
|
|
872
|
+
} else {
|
|
873
|
+
console.log(
|
|
874
|
+
` ${GREEN}${BOLD}🎉 Marathon complete! All features passing.${RESET}`,
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
console.log();
|
|
749
878
|
}
|
|
750
879
|
|
|
751
880
|
/**
|
|
752
881
|
* Archive the current marathon and reset.
|
|
753
882
|
*/
|
|
754
883
|
function cmdReset() {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
884
|
+
if (!isActive()) {
|
|
885
|
+
console.log(`${YELLOW}No active marathon to reset.${RESET}`);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const featureList = readJSON(FEATURE_LIST_FILE);
|
|
890
|
+
const { total, passing } = featureList
|
|
891
|
+
? countFeatures(featureList)
|
|
892
|
+
: { total: 0, passing: 0 };
|
|
893
|
+
|
|
894
|
+
// Archive current state
|
|
895
|
+
const archiveTimestamp = new Date()
|
|
896
|
+
.toISOString()
|
|
897
|
+
.replace(/[:.]/g, "-")
|
|
898
|
+
.slice(0, 19);
|
|
899
|
+
const archivePath = path.join(ARCHIVE_DIR, archiveTimestamp);
|
|
900
|
+
fs.mkdirSync(archivePath, { recursive: true });
|
|
901
|
+
|
|
902
|
+
if (fs.existsSync(FEATURE_LIST_FILE)) {
|
|
903
|
+
fs.cpSync(FEATURE_LIST_FILE, path.join(archivePath, "feature_list.json"));
|
|
904
|
+
}
|
|
905
|
+
if (fs.existsSync(PROGRESS_FILE)) {
|
|
906
|
+
fs.cpSync(PROGRESS_FILE, path.join(archivePath, "progress.json"));
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// Remove current state files
|
|
910
|
+
if (fs.existsSync(FEATURE_LIST_FILE)) fs.unlinkSync(FEATURE_LIST_FILE);
|
|
911
|
+
if (fs.existsSync(PROGRESS_FILE)) fs.unlinkSync(PROGRESS_FILE);
|
|
912
|
+
|
|
913
|
+
ok(`Marathon archived to: ${archivePath}`);
|
|
914
|
+
console.log(
|
|
915
|
+
` ${DIM}Progress at archive: ${passing}/${total} features passing${RESET}`,
|
|
916
|
+
);
|
|
917
|
+
console.log(
|
|
918
|
+
` ${DIM}Start a new marathon with: marathon_harness.js init "new spec"${RESET}`,
|
|
919
|
+
);
|
|
920
|
+
console.log();
|
|
783
921
|
}
|
|
784
922
|
|
|
785
923
|
// ── Help ─────────────────────────────────────────────────────────────────────
|
|
786
924
|
|
|
787
925
|
function showHelp() {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
926
|
+
console.log(banner("marathon_harness.js", { Mode: "HELP" }));
|
|
927
|
+
console.log();
|
|
928
|
+
console.log(` ${BOLD}Long-Running Agent Harness${RESET}`);
|
|
929
|
+
console.log(
|
|
930
|
+
` ${DIM}Tracks features, progress, and sessions for multi-session agent workflows.${RESET}`,
|
|
931
|
+
);
|
|
932
|
+
console.log();
|
|
933
|
+
|
|
934
|
+
const cmd = (name, desc) =>
|
|
935
|
+
console.log(` ${CYAN}${name.padEnd(16)}${RESET} ${desc}`);
|
|
936
|
+
|
|
937
|
+
cmd('init "spec"', "Start a new marathon with the given specification");
|
|
938
|
+
cmd("status", "Show progress dashboard");
|
|
939
|
+
cmd("next", "Show the next unfinished feature");
|
|
940
|
+
cmd("mark <id> pass", "Mark a feature as passing");
|
|
941
|
+
cmd("mark <id> fail", 'Mark a feature as failing (optional: "reason")');
|
|
942
|
+
cmd('log "note"', "Add a timestamped progress note");
|
|
943
|
+
cmd('distill "rule"', "Save an architectural rule or lesson to memory");
|
|
944
|
+
cmd(
|
|
945
|
+
"session-start",
|
|
946
|
+
"Begin a new work session (reads state, shows bearings)",
|
|
947
|
+
);
|
|
948
|
+
cmd("session-end", "End session with optional summary");
|
|
949
|
+
cmd(
|
|
950
|
+
"add-feature",
|
|
951
|
+
"Add a feature (supports --deps=1,2,3 for DAG dependencies)",
|
|
952
|
+
);
|
|
953
|
+
cmd("reset", "Archive current marathon and start fresh");
|
|
954
|
+
console.log();
|
|
808
955
|
}
|
|
809
956
|
|
|
810
957
|
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
811
958
|
|
|
812
959
|
function main() {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
960
|
+
const args = process.argv.slice(2);
|
|
961
|
+
|
|
962
|
+
if (
|
|
963
|
+
args.length === 0 ||
|
|
964
|
+
args[0] === "help" ||
|
|
965
|
+
args[0] === "--help" ||
|
|
966
|
+
args[0] === "-h"
|
|
967
|
+
) {
|
|
968
|
+
showHelp();
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
const cmd = args[0].toLowerCase();
|
|
973
|
+
|
|
974
|
+
if (!VALID_COMMANDS.has(cmd)) {
|
|
975
|
+
console.error(`${RED}Unknown command: "${cmd}"${RESET}`);
|
|
976
|
+
console.error(`Valid commands: ${[...VALID_COMMANDS].sort().join(", ")}`);
|
|
977
|
+
process.exit(1);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
switch (cmd) {
|
|
981
|
+
case "init": {
|
|
982
|
+
const spec = args.slice(1).join(" ").trim();
|
|
983
|
+
cmdInit(spec);
|
|
984
|
+
break;
|
|
818
985
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
986
|
+
case "status":
|
|
987
|
+
cmdStatus();
|
|
988
|
+
break;
|
|
989
|
+
case "next":
|
|
990
|
+
cmdNext();
|
|
991
|
+
break;
|
|
992
|
+
case "mark": {
|
|
993
|
+
const id = parseInt(args[1], 10);
|
|
994
|
+
const verdict = (args[2] || "").toLowerCase();
|
|
995
|
+
const reason = args.slice(3).join(" ").trim();
|
|
996
|
+
if (isNaN(id)) {
|
|
997
|
+
console.error(
|
|
998
|
+
`${RED}❌ Feature ID required. Usage: mark <id> pass|fail "reason"${RESET}`,
|
|
999
|
+
);
|
|
825
1000
|
process.exit(1);
|
|
1001
|
+
}
|
|
1002
|
+
cmdMark(id, verdict, reason);
|
|
1003
|
+
break;
|
|
826
1004
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
break;
|
|
855
|
-
}
|
|
856
|
-
case 'session-start':
|
|
857
|
-
cmdSessionStart();
|
|
858
|
-
break;
|
|
859
|
-
case 'session-end': {
|
|
860
|
-
const summary = args.slice(1).join(' ').trim() || null;
|
|
861
|
-
cmdSessionEnd(summary);
|
|
862
|
-
break;
|
|
863
|
-
}
|
|
864
|
-
case 'add-feature': {
|
|
865
|
-
const category = args[1] || '';
|
|
866
|
-
const description = args[2] || '';
|
|
867
|
-
let steps = args.slice(3);
|
|
868
|
-
let deps = [];
|
|
869
|
-
|
|
870
|
-
steps = steps.filter(step => {
|
|
871
|
-
if (step.startsWith('--deps=')) {
|
|
872
|
-
deps = step.replace('--deps=', '').split(',').map(Number).filter(n => !isNaN(n));
|
|
873
|
-
return false;
|
|
874
|
-
}
|
|
875
|
-
return true;
|
|
876
|
-
});
|
|
877
|
-
|
|
878
|
-
cmdAddFeature(category, description, steps, deps);
|
|
879
|
-
break;
|
|
880
|
-
}
|
|
881
|
-
case 'distill': {
|
|
882
|
-
const lesson = args.slice(1).join(' ').trim();
|
|
883
|
-
cmdDistill(lesson);
|
|
884
|
-
break;
|
|
1005
|
+
case "log": {
|
|
1006
|
+
const message = args.slice(1).join(" ").trim();
|
|
1007
|
+
cmdLog(message);
|
|
1008
|
+
break;
|
|
1009
|
+
}
|
|
1010
|
+
case "session-start":
|
|
1011
|
+
cmdSessionStart();
|
|
1012
|
+
break;
|
|
1013
|
+
case "session-end": {
|
|
1014
|
+
const summary = args.slice(1).join(" ").trim() || null;
|
|
1015
|
+
cmdSessionEnd(summary);
|
|
1016
|
+
break;
|
|
1017
|
+
}
|
|
1018
|
+
case "add-feature": {
|
|
1019
|
+
const category = args[1] || "";
|
|
1020
|
+
const description = args[2] || "";
|
|
1021
|
+
let steps = args.slice(3);
|
|
1022
|
+
let deps = [];
|
|
1023
|
+
|
|
1024
|
+
steps = steps.filter((step) => {
|
|
1025
|
+
if (step.startsWith("--deps=")) {
|
|
1026
|
+
deps = step
|
|
1027
|
+
.replace("--deps=", "")
|
|
1028
|
+
.split(",")
|
|
1029
|
+
.map(Number)
|
|
1030
|
+
.filter((n) => !isNaN(n));
|
|
1031
|
+
return false;
|
|
885
1032
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
1033
|
+
return true;
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
cmdAddFeature(category, description, steps, deps);
|
|
1037
|
+
break;
|
|
1038
|
+
}
|
|
1039
|
+
case "distill": {
|
|
1040
|
+
const lesson = args.slice(1).join(" ").trim();
|
|
1041
|
+
cmdDistill(lesson);
|
|
1042
|
+
break;
|
|
891
1043
|
}
|
|
1044
|
+
case "reset":
|
|
1045
|
+
cmdReset();
|
|
1046
|
+
break;
|
|
1047
|
+
default:
|
|
1048
|
+
showHelp();
|
|
1049
|
+
}
|
|
892
1050
|
}
|
|
893
1051
|
|
|
894
1052
|
if (require.main === module) {
|
|
895
|
-
|
|
1053
|
+
main();
|
|
896
1054
|
}
|