@scanton/phase2s 1.1.0 → 1.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/README.md +16 -1
- package/dist/src/cli/doctor.d.ts +39 -0
- package/dist/src/cli/doctor.d.ts.map +1 -0
- package/dist/src/cli/doctor.js +244 -0
- package/dist/src/cli/doctor.js.map +1 -0
- package/dist/src/cli/goal.d.ts +32 -1
- package/dist/src/cli/goal.d.ts.map +1 -1
- package/dist/src/cli/goal.js +181 -14
- package/dist/src/cli/goal.js.map +1 -1
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +115 -9
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/cli/init.d.ts +73 -0
- package/dist/src/cli/init.d.ts.map +1 -0
- package/dist/src/cli/init.js +366 -0
- package/dist/src/cli/init.js.map +1 -0
- package/dist/src/cli/lint.d.ts +32 -0
- package/dist/src/cli/lint.d.ts.map +1 -0
- package/dist/src/cli/lint.js +140 -0
- package/dist/src/cli/lint.js.map +1 -0
- package/dist/src/cli/report.d.ts +62 -0
- package/dist/src/cli/report.d.ts.map +1 -0
- package/dist/src/cli/report.js +188 -0
- package/dist/src/cli/report.js.map +1 -0
- package/dist/src/cli/upgrade.d.ts +35 -0
- package/dist/src/cli/upgrade.d.ts.map +1 -0
- package/dist/src/cli/upgrade.js +126 -0
- package/dist/src/cli/upgrade.js.map +1 -0
- package/dist/src/core/config.d.ts +60 -8
- package/dist/src/core/config.d.ts.map +1 -1
- package/dist/src/core/config.js +31 -1
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/notify.d.ts +41 -0
- package/dist/src/core/notify.d.ts.map +1 -0
- package/dist/src/core/notify.js +153 -0
- package/dist/src/core/notify.js.map +1 -0
- package/dist/src/core/run-logger.d.ts +83 -0
- package/dist/src/core/run-logger.d.ts.map +1 -0
- package/dist/src/core/run-logger.js +70 -0
- package/dist/src/core/run-logger.js.map +1 -0
- package/dist/src/mcp/server.d.ts +9 -0
- package/dist/src/mcp/server.d.ts.map +1 -1
- package/dist/src/mcp/server.js +148 -1
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/providers/gemini.d.ts +29 -0
- package/dist/src/providers/gemini.d.ts.map +1 -0
- package/dist/src/providers/gemini.js +47 -0
- package/dist/src/providers/gemini.js.map +1 -0
- package/dist/src/providers/index.d.ts +2 -0
- package/dist/src/providers/index.d.ts.map +1 -1
- package/dist/src/providers/index.js +8 -0
- package/dist/src/providers/index.js.map +1 -1
- package/dist/src/providers/openrouter.d.ts +25 -0
- package/dist/src/providers/openrouter.d.ts.map +1 -0
- package/dist/src/providers/openrouter.js +43 -0
- package/dist/src/providers/openrouter.js.map +1 -0
- package/dist/src/tools/registry.d.ts +5 -3
- package/dist/src/tools/registry.d.ts.map +1 -1
- package/dist/src/tools/registry.js +35 -12
- package/dist/src/tools/registry.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* phase2s lint — validate a 5-pillar spec before running it.
|
|
3
|
+
*
|
|
4
|
+
* Runs a set of structural checks on a parsed spec and reports errors (blocking)
|
|
5
|
+
* and warnings (advisory). Designed to catch broken specs before the 20-minute
|
|
6
|
+
* dark-factory run begins.
|
|
7
|
+
*
|
|
8
|
+
* Pure check functions are exported for testing. runLint() handles all IO.
|
|
9
|
+
* Exit code reflects result: 0 = no errors (warnings OK), 1 = one or more errors.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
12
|
+
import { resolve } from "node:path";
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
import { parseSpec } from "../core/spec-parser.js";
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Pure validation (exported for testing)
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
/**
|
|
19
|
+
* Validate a parsed Spec for structural completeness.
|
|
20
|
+
* Returns all issues found — does not fail fast.
|
|
21
|
+
*/
|
|
22
|
+
export function lintSpec(spec) {
|
|
23
|
+
const issues = [];
|
|
24
|
+
// ── Errors (blocking — goal executor will fail or produce wrong results) ──
|
|
25
|
+
if (!spec.title || spec.title === "Untitled Spec") {
|
|
26
|
+
issues.push({
|
|
27
|
+
severity: "error",
|
|
28
|
+
message: "spec has no title",
|
|
29
|
+
fix: "Add a # Title line at the top of the spec file",
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if (!spec.problemStatement || spec.problemStatement.trim() === "") {
|
|
33
|
+
issues.push({
|
|
34
|
+
severity: "error",
|
|
35
|
+
message: "## Problem Statement section is empty",
|
|
36
|
+
fix: "Describe what problem this spec solves",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (spec.decomposition.length === 0) {
|
|
40
|
+
issues.push({
|
|
41
|
+
severity: "error",
|
|
42
|
+
message: "## Decomposition section has no sub-tasks",
|
|
43
|
+
fix: "Add at least one sub-task with ### Name, Input, Output, and Success Criteria",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (spec.acceptanceCriteria.length === 0) {
|
|
47
|
+
issues.push({
|
|
48
|
+
severity: "error",
|
|
49
|
+
message: "## Acceptance Criteria section is empty",
|
|
50
|
+
fix: "Add at least one criterion — the goal executor checks these to determine success",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// ── Warnings (advisory — goal executor will run but may produce poor results) ──
|
|
54
|
+
if (spec.evalCommand === "npm test") {
|
|
55
|
+
issues.push({
|
|
56
|
+
severity: "warn",
|
|
57
|
+
message: 'evalCommand is "npm test" (the default) — confirm this is correct for your project',
|
|
58
|
+
fix: 'Add "eval: <your-test-command>" to the spec if your project uses a different test runner',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
for (const subtask of spec.decomposition) {
|
|
62
|
+
if (!subtask.successCriteria || subtask.successCriteria.trim() === "") {
|
|
63
|
+
issues.push({
|
|
64
|
+
severity: "warn",
|
|
65
|
+
message: `sub-task "${subtask.name || "<unnamed>"}" has no Success Criteria`,
|
|
66
|
+
fix: "Add a Success Criteria line so satori knows when the sub-task is done",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (!subtask.name || subtask.name.trim() === "") {
|
|
70
|
+
issues.push({
|
|
71
|
+
severity: "warn",
|
|
72
|
+
message: "a sub-task has an empty name",
|
|
73
|
+
fix: "Give each sub-task a descriptive name under ### heading",
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const errorCount = issues.filter((i) => i.severity === "error").length;
|
|
78
|
+
return { ok: errorCount === 0, issues };
|
|
79
|
+
}
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// runLint — entry point
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
/**
|
|
84
|
+
* Read a spec file, parse it, run lintSpec(), and print results.
|
|
85
|
+
* Returns true if there are no errors (may have warnings).
|
|
86
|
+
* Returns false if any errors were found.
|
|
87
|
+
*/
|
|
88
|
+
export async function runLint(specFilePath) {
|
|
89
|
+
const absPath = resolve(specFilePath);
|
|
90
|
+
if (!existsSync(absPath)) {
|
|
91
|
+
console.error(chalk.red(` ✗ File not found: ${specFilePath}`));
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
let content;
|
|
95
|
+
try {
|
|
96
|
+
content = readFileSync(absPath, "utf8");
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
console.error(chalk.red(` ✗ Could not read ${specFilePath}: ${err.message}`));
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
const spec = parseSpec(content);
|
|
103
|
+
const result = lintSpec(spec);
|
|
104
|
+
const errors = result.issues.filter((i) => i.severity === "error");
|
|
105
|
+
const warns = result.issues.filter((i) => i.severity === "warn");
|
|
106
|
+
console.log(chalk.bold(`\n Linting ${specFilePath}\n`));
|
|
107
|
+
console.log(chalk.dim(` Title: ${spec.title}`));
|
|
108
|
+
console.log(chalk.dim(` Sub-tasks: ${spec.decomposition.length}`));
|
|
109
|
+
console.log(chalk.dim(` Criteria: ${spec.acceptanceCriteria.length}`));
|
|
110
|
+
console.log(chalk.dim(` Eval: ${spec.evalCommand}`));
|
|
111
|
+
console.log("");
|
|
112
|
+
if (result.issues.length === 0) {
|
|
113
|
+
console.log(chalk.green(" ✓ Spec looks good. Ready to run phase2s goal."));
|
|
114
|
+
console.log("");
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
for (const issue of result.issues) {
|
|
118
|
+
if (issue.severity === "error") {
|
|
119
|
+
console.log(chalk.red(` ✗ ${issue.message}`));
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
console.log(chalk.yellow(` ⚠ ${issue.message}`));
|
|
123
|
+
}
|
|
124
|
+
if (issue.fix) {
|
|
125
|
+
console.log(chalk.dim(` ${issue.fix}`));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
console.log("");
|
|
129
|
+
if (errors.length > 0) {
|
|
130
|
+
const plural = errors.length === 1 ? "error" : "errors";
|
|
131
|
+
const warnNote = warns.length > 0 ? `, ${warns.length} warning${warns.length === 1 ? "" : "s"}` : "";
|
|
132
|
+
console.log(chalk.red(` ${errors.length} ${plural}${warnNote} found. Fix errors before running phase2s goal.`));
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
console.log(chalk.yellow(` ${warns.length} warning${warns.length === 1 ? "" : "s"} — spec is runnable but review the notes above.`));
|
|
136
|
+
}
|
|
137
|
+
console.log("");
|
|
138
|
+
return result.ok;
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=lint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint.js","sourceRoot":"","sources":["../../../src/cli/lint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run log report viewer.
|
|
3
|
+
*
|
|
4
|
+
* Parses a structured JSONL run log (produced by RunLogger) and renders a
|
|
5
|
+
* chalk-colored human-readable summary of the dark factory run.
|
|
6
|
+
*
|
|
7
|
+
* Usage: phase2s report <logfile.jsonl>
|
|
8
|
+
*
|
|
9
|
+
* The report shows:
|
|
10
|
+
* - Spec filename
|
|
11
|
+
* - Pre-execution review verdict (if applicable)
|
|
12
|
+
* - Per-attempt: sub-task status + duration, eval command, criteria verdicts
|
|
13
|
+
* - Final outcome + total duration
|
|
14
|
+
*/
|
|
15
|
+
import type { RunEvent } from "../core/run-logger.js";
|
|
16
|
+
export interface SubtaskReport {
|
|
17
|
+
index: number;
|
|
18
|
+
name: string;
|
|
19
|
+
status: "passed" | "failed";
|
|
20
|
+
durationMs?: number;
|
|
21
|
+
failureContext?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface AttemptReport {
|
|
24
|
+
attempt: number;
|
|
25
|
+
subtasks: SubtaskReport[];
|
|
26
|
+
evalCommand?: string;
|
|
27
|
+
criteria: Array<{
|
|
28
|
+
criterion: string;
|
|
29
|
+
passed: boolean;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
export interface RunReport {
|
|
33
|
+
specFile: string;
|
|
34
|
+
maxAttempts: number;
|
|
35
|
+
challenged: boolean;
|
|
36
|
+
challengeVerdict?: string;
|
|
37
|
+
attempts: AttemptReport[];
|
|
38
|
+
finalSuccess: boolean;
|
|
39
|
+
finalAttempts: number;
|
|
40
|
+
durationMs?: number;
|
|
41
|
+
error?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Parse a JSONL run log file into a list of timestamped RunEvents.
|
|
45
|
+
* Throws if the file cannot be read.
|
|
46
|
+
*/
|
|
47
|
+
export declare function parseRunLog(logPath: string): Array<RunEvent & {
|
|
48
|
+
ts: string;
|
|
49
|
+
}>;
|
|
50
|
+
/**
|
|
51
|
+
* Build a structured RunReport from a list of timestamped RunEvents.
|
|
52
|
+
* Unknown or out-of-order events are silently ignored.
|
|
53
|
+
*/
|
|
54
|
+
export declare function buildRunReport(events: Array<RunEvent & {
|
|
55
|
+
ts: string;
|
|
56
|
+
}>): RunReport;
|
|
57
|
+
/**
|
|
58
|
+
* Format a RunReport as a chalk-colored string for terminal display.
|
|
59
|
+
* Safe to call in non-TTY contexts — chalk auto-detects color support.
|
|
60
|
+
*/
|
|
61
|
+
export declare function formatRunReport(report: RunReport): string;
|
|
62
|
+
//# sourceMappingURL=report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../../src/cli/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAMtD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC,QAAQ,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAO7E;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,SAAS,CA+FlF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CA2DzD"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run log report viewer.
|
|
3
|
+
*
|
|
4
|
+
* Parses a structured JSONL run log (produced by RunLogger) and renders a
|
|
5
|
+
* chalk-colored human-readable summary of the dark factory run.
|
|
6
|
+
*
|
|
7
|
+
* Usage: phase2s report <logfile.jsonl>
|
|
8
|
+
*
|
|
9
|
+
* The report shows:
|
|
10
|
+
* - Spec filename
|
|
11
|
+
* - Pre-execution review verdict (if applicable)
|
|
12
|
+
* - Per-attempt: sub-task status + duration, eval command, criteria verdicts
|
|
13
|
+
* - Final outcome + total duration
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync } from "node:fs";
|
|
16
|
+
import { basename } from "node:path";
|
|
17
|
+
import chalk from "chalk";
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Public API
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
/**
|
|
22
|
+
* Parse a JSONL run log file into a list of timestamped RunEvents.
|
|
23
|
+
* Throws if the file cannot be read.
|
|
24
|
+
*/
|
|
25
|
+
export function parseRunLog(logPath) {
|
|
26
|
+
const raw = readFileSync(logPath, "utf8");
|
|
27
|
+
return raw
|
|
28
|
+
.trim()
|
|
29
|
+
.split("\n")
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
.map((line) => JSON.parse(line));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build a structured RunReport from a list of timestamped RunEvents.
|
|
35
|
+
* Unknown or out-of-order events are silently ignored.
|
|
36
|
+
*/
|
|
37
|
+
export function buildRunReport(events) {
|
|
38
|
+
const report = {
|
|
39
|
+
specFile: "",
|
|
40
|
+
maxAttempts: 3,
|
|
41
|
+
challenged: false,
|
|
42
|
+
attempts: [],
|
|
43
|
+
finalSuccess: false,
|
|
44
|
+
finalAttempts: 0,
|
|
45
|
+
};
|
|
46
|
+
let startTs = null;
|
|
47
|
+
let currentAttempt = null;
|
|
48
|
+
// Track start times for duration computation
|
|
49
|
+
const subtaskStartTimes = new Map(); // key: `${attempt}:${index}`
|
|
50
|
+
const subtaskNames = new Map(); // key: index
|
|
51
|
+
for (const raw of events) {
|
|
52
|
+
const ts = new Date(raw.ts);
|
|
53
|
+
const event = raw;
|
|
54
|
+
switch (event.event) {
|
|
55
|
+
case "goal_started":
|
|
56
|
+
report.specFile = event.specFile;
|
|
57
|
+
report.maxAttempts = event.maxAttempts;
|
|
58
|
+
startTs = ts;
|
|
59
|
+
break;
|
|
60
|
+
case "plan_review_completed":
|
|
61
|
+
if (event.verdict !== "APPROVED") {
|
|
62
|
+
report.challenged = true;
|
|
63
|
+
report.challengeVerdict = event.verdict;
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
case "attempt_started":
|
|
67
|
+
currentAttempt = {
|
|
68
|
+
attempt: event.attempt,
|
|
69
|
+
subtasks: [],
|
|
70
|
+
criteria: [],
|
|
71
|
+
};
|
|
72
|
+
report.attempts.push(currentAttempt);
|
|
73
|
+
break;
|
|
74
|
+
case "subtask_started":
|
|
75
|
+
subtaskNames.set(event.index, event.name);
|
|
76
|
+
subtaskStartTimes.set(`${event.attempt}:${event.index}`, ts);
|
|
77
|
+
break;
|
|
78
|
+
case "subtask_completed": {
|
|
79
|
+
if (!currentAttempt)
|
|
80
|
+
break;
|
|
81
|
+
const name = subtaskNames.get(event.index) ?? `Sub-task ${event.index + 1}`;
|
|
82
|
+
const startTime = subtaskStartTimes.get(`${event.attempt}:${event.index}`);
|
|
83
|
+
const durationMs = startTime ? ts.getTime() - startTime.getTime() : undefined;
|
|
84
|
+
currentAttempt.subtasks.push({
|
|
85
|
+
index: event.index,
|
|
86
|
+
name,
|
|
87
|
+
status: event.status,
|
|
88
|
+
durationMs,
|
|
89
|
+
failureContext: event.failureContext,
|
|
90
|
+
});
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
case "eval_started":
|
|
94
|
+
if (currentAttempt)
|
|
95
|
+
currentAttempt.evalCommand = event.command;
|
|
96
|
+
break;
|
|
97
|
+
case "criteria_checked":
|
|
98
|
+
if (currentAttempt) {
|
|
99
|
+
for (const [criterion, passed] of Object.entries(event.results)) {
|
|
100
|
+
currentAttempt.criteria.push({ criterion, passed: Boolean(passed) });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
case "goal_completed":
|
|
105
|
+
report.finalSuccess = event.success;
|
|
106
|
+
report.finalAttempts = event.attempts;
|
|
107
|
+
if (startTs) {
|
|
108
|
+
report.durationMs = ts.getTime() - startTs.getTime();
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
case "goal_error":
|
|
112
|
+
report.error = event.message;
|
|
113
|
+
break;
|
|
114
|
+
default:
|
|
115
|
+
// plan_review_started, eval_completed — no action needed for report
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return report;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Format a RunReport as a chalk-colored string for terminal display.
|
|
123
|
+
* Safe to call in non-TTY contexts — chalk auto-detects color support.
|
|
124
|
+
*/
|
|
125
|
+
export function formatRunReport(report) {
|
|
126
|
+
const lines = [];
|
|
127
|
+
const specName = basename(report.specFile);
|
|
128
|
+
lines.push(chalk.bold(`Goal: ${specName}`));
|
|
129
|
+
if (report.error) {
|
|
130
|
+
lines.push(chalk.red(`\nError: ${report.error}`));
|
|
131
|
+
return lines.join("\n");
|
|
132
|
+
}
|
|
133
|
+
if (report.challenged) {
|
|
134
|
+
const verdict = report.challengeVerdict ?? "CHALLENGED";
|
|
135
|
+
lines.push("");
|
|
136
|
+
lines.push(` ${chalk.yellow("⚠")} Pre-execution review: ${chalk.yellow(verdict)}`);
|
|
137
|
+
lines.push(chalk.dim(" Run was halted before execution."));
|
|
138
|
+
return lines.join("\n");
|
|
139
|
+
}
|
|
140
|
+
for (const attempt of report.attempts) {
|
|
141
|
+
lines.push(`\n ${chalk.dim(`Attempt ${attempt.attempt}/${report.maxAttempts}`)}`);
|
|
142
|
+
for (const subtask of attempt.subtasks) {
|
|
143
|
+
const icon = subtask.status === "passed"
|
|
144
|
+
? chalk.green("✓")
|
|
145
|
+
: chalk.red("✗");
|
|
146
|
+
const dur = subtask.durationMs !== undefined
|
|
147
|
+
? chalk.dim(` (${formatDuration(subtask.durationMs)})`)
|
|
148
|
+
: "";
|
|
149
|
+
lines.push(` ${icon} ${subtask.name}${dur}`);
|
|
150
|
+
}
|
|
151
|
+
if (attempt.evalCommand) {
|
|
152
|
+
lines.push(chalk.dim(`\n Eval: ${attempt.evalCommand}`));
|
|
153
|
+
}
|
|
154
|
+
if (attempt.criteria.length > 0) {
|
|
155
|
+
lines.push("\n Criteria:");
|
|
156
|
+
for (const c of attempt.criteria) {
|
|
157
|
+
const icon = c.passed ? chalk.green("✓") : chalk.red("✗");
|
|
158
|
+
lines.push(` ${icon} ${c.criterion}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const dur = report.durationMs !== undefined
|
|
163
|
+
? chalk.dim(` — ${formatDuration(report.durationMs)}`)
|
|
164
|
+
: "";
|
|
165
|
+
const attemptsStr = `${report.finalAttempts} attempt${report.finalAttempts !== 1 ? "s" : ""}`;
|
|
166
|
+
if (report.finalSuccess) {
|
|
167
|
+
lines.push(`\n${chalk.green("✓")} ${chalk.bold("Goal complete")} — ${attemptsStr}${dur}`);
|
|
168
|
+
}
|
|
169
|
+
else if (report.finalAttempts > 0) {
|
|
170
|
+
lines.push(`\n${chalk.red("✗")} ${chalk.bold("Goal failed")} — ${attemptsStr}${dur}`);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
lines.push(`\n${chalk.dim("Goal did not run.")}`);
|
|
174
|
+
}
|
|
175
|
+
return lines.join("\n");
|
|
176
|
+
}
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// Helpers
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
function formatDuration(ms) {
|
|
181
|
+
const totalSeconds = Math.floor(ms / 1000);
|
|
182
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
183
|
+
const seconds = totalSeconds % 60;
|
|
184
|
+
if (minutes === 0)
|
|
185
|
+
return `${seconds}s`;
|
|
186
|
+
return `${minutes}m ${seconds}s`;
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../../src/cli/report.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* phase2s upgrade — self-update command.
|
|
3
|
+
*
|
|
4
|
+
* Checks npm registry for the latest published version and offers to install
|
|
5
|
+
* it via `npm install -g @scanton/phase2s`.
|
|
6
|
+
*
|
|
7
|
+
* Pure functions (checkLatestVersion, isUpdateAvailable, parseVersion) are
|
|
8
|
+
* exported for testing. runUpgrade() handles all IO.
|
|
9
|
+
*/
|
|
10
|
+
export interface VersionParts {
|
|
11
|
+
major: number;
|
|
12
|
+
minor: number;
|
|
13
|
+
patch: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parse a semver string into major/minor/patch numbers.
|
|
17
|
+
* Returns null if the string is not a valid semver.
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseVersion(version: string): VersionParts | null;
|
|
20
|
+
/**
|
|
21
|
+
* Returns true if `latest` is strictly newer than `current`.
|
|
22
|
+
* Returns false if they are equal or `current` is newer.
|
|
23
|
+
* Returns false (safe default) if either version is unparseable.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isUpdateAvailable(current: string, latest: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Fetch the latest published version of a package from the npm registry.
|
|
28
|
+
* Returns null if the request fails or the response is unparseable.
|
|
29
|
+
* Times out after 5 seconds.
|
|
30
|
+
*/
|
|
31
|
+
export declare function checkLatestVersion(packageName: string, registryBase?: string): Promise<string | null>;
|
|
32
|
+
export declare function runUpgrade(currentVersion: string, opts?: {
|
|
33
|
+
check?: boolean;
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
//# sourceMappingURL=upgrade.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../../src/cli/upgrade.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAWH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAMD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAKjE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAO1E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,YAAY,SAA+B,GAC1C,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA4BxB;AAMD,wBAAsB,UAAU,CAC9B,cAAc,EAAE,MAAM,EACtB,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAC7B,OAAO,CAAC,IAAI,CAAC,CA+Df"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* phase2s upgrade — self-update command.
|
|
3
|
+
*
|
|
4
|
+
* Checks npm registry for the latest published version and offers to install
|
|
5
|
+
* it via `npm install -g @scanton/phase2s`.
|
|
6
|
+
*
|
|
7
|
+
* Pure functions (checkLatestVersion, isUpdateAvailable, parseVersion) are
|
|
8
|
+
* exported for testing. runUpgrade() handles all IO.
|
|
9
|
+
*/
|
|
10
|
+
import { get } from "node:https";
|
|
11
|
+
import { spawnSync } from "node:child_process";
|
|
12
|
+
import { createInterface } from "node:readline";
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Pure helpers (exported for testing)
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
/**
|
|
18
|
+
* Parse a semver string into major/minor/patch numbers.
|
|
19
|
+
* Returns null if the string is not a valid semver.
|
|
20
|
+
*/
|
|
21
|
+
export function parseVersion(version) {
|
|
22
|
+
const clean = version.replace(/^v/, "");
|
|
23
|
+
const parts = clean.split(".").map(Number);
|
|
24
|
+
if (parts.length !== 3 || parts.some((n) => isNaN(n) || n < 0))
|
|
25
|
+
return null;
|
|
26
|
+
return { major: parts[0], minor: parts[1], patch: parts[2] };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Returns true if `latest` is strictly newer than `current`.
|
|
30
|
+
* Returns false if they are equal or `current` is newer.
|
|
31
|
+
* Returns false (safe default) if either version is unparseable.
|
|
32
|
+
*/
|
|
33
|
+
export function isUpdateAvailable(current, latest) {
|
|
34
|
+
const c = parseVersion(current);
|
|
35
|
+
const l = parseVersion(latest);
|
|
36
|
+
if (!c || !l)
|
|
37
|
+
return false;
|
|
38
|
+
if (l.major !== c.major)
|
|
39
|
+
return l.major > c.major;
|
|
40
|
+
if (l.minor !== c.minor)
|
|
41
|
+
return l.minor > c.minor;
|
|
42
|
+
return l.patch > c.patch;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Fetch the latest published version of a package from the npm registry.
|
|
46
|
+
* Returns null if the request fails or the response is unparseable.
|
|
47
|
+
* Times out after 5 seconds.
|
|
48
|
+
*/
|
|
49
|
+
export function checkLatestVersion(packageName, registryBase = "https://registry.npmjs.org") {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
const url = `${registryBase}/${encodeURIComponent(packageName)}/latest`;
|
|
52
|
+
const req = get(url, { headers: { Accept: "application/json" } }, (res) => {
|
|
53
|
+
let data = "";
|
|
54
|
+
res.on("data", (chunk) => {
|
|
55
|
+
data += chunk.toString();
|
|
56
|
+
});
|
|
57
|
+
res.on("end", () => {
|
|
58
|
+
try {
|
|
59
|
+
const json = JSON.parse(data);
|
|
60
|
+
resolve(typeof json.version === "string" ? json.version : null);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
resolve(null);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
res.on("error", () => resolve(null));
|
|
67
|
+
});
|
|
68
|
+
req.on("error", () => resolve(null));
|
|
69
|
+
req.setTimeout(5000, () => {
|
|
70
|
+
req.destroy();
|
|
71
|
+
resolve(null);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// runUpgrade — entry point
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
export async function runUpgrade(currentVersion, opts = {}) {
|
|
79
|
+
process.stdout.write(chalk.dim(" Checking for updates...\n"));
|
|
80
|
+
const latest = await checkLatestVersion("@scanton/phase2s");
|
|
81
|
+
if (!latest) {
|
|
82
|
+
console.log(chalk.yellow(" Could not reach npm registry. Check your network connection."));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (!isUpdateAvailable(currentVersion, latest)) {
|
|
86
|
+
console.log(chalk.green(` You're on the latest version`) +
|
|
87
|
+
chalk.dim(` (v${currentVersion})`));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
console.log(chalk.bold(`\n phase2s v${currentVersion}`) +
|
|
91
|
+
chalk.dim(" → ") +
|
|
92
|
+
chalk.green(`v${latest} available`));
|
|
93
|
+
if (opts.check) {
|
|
94
|
+
// --check: just report, don't prompt
|
|
95
|
+
console.log(chalk.dim(` Run: npm install -g @scanton/phase2s`));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// Interactive prompt
|
|
99
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
100
|
+
const answer = await new Promise((resolve) => {
|
|
101
|
+
rl.question(chalk.cyan(" Upgrade now? [y/N] "), (ans) => {
|
|
102
|
+
rl.close();
|
|
103
|
+
resolve(ans.trim().toLowerCase());
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
if (answer !== "y" && answer !== "yes") {
|
|
107
|
+
console.log(chalk.dim(" Skipped. Run `npm install -g @scanton/phase2s` when ready."));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
console.log(chalk.dim("\n Running: npm install -g @scanton/phase2s\n"));
|
|
111
|
+
const result = spawnSync("npm", ["install", "-g", "@scanton/phase2s"], {
|
|
112
|
+
stdio: "inherit",
|
|
113
|
+
shell: false,
|
|
114
|
+
});
|
|
115
|
+
if (result.status === 0) {
|
|
116
|
+
console.log(chalk.green(`\n Upgraded to v${latest}. Restart phase2s to use the new version.`));
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
console.log(chalk.red("\n npm install failed. Try running manually:") +
|
|
120
|
+
chalk.bold("\n npm install -g @scanton/phase2s"));
|
|
121
|
+
if (result.error) {
|
|
122
|
+
console.log(chalk.dim(` Error: ${result.error.message}`));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=upgrade.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../../src/cli/upgrade.ts"],"names":[],"mappings":"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"}
|