@scanton/phase2s 1.1.0 → 1.9.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 (61) hide show
  1. package/README.md +19 -1
  2. package/dist/src/cli/doctor.d.ts +39 -0
  3. package/dist/src/cli/doctor.d.ts.map +1 -0
  4. package/dist/src/cli/doctor.js +244 -0
  5. package/dist/src/cli/doctor.js.map +1 -0
  6. package/dist/src/cli/goal.d.ts +36 -1
  7. package/dist/src/cli/goal.d.ts.map +1 -1
  8. package/dist/src/cli/goal.js +243 -16
  9. package/dist/src/cli/goal.js.map +1 -1
  10. package/dist/src/cli/index.d.ts.map +1 -1
  11. package/dist/src/cli/index.js +117 -9
  12. package/dist/src/cli/index.js.map +1 -1
  13. package/dist/src/cli/init.d.ts +73 -0
  14. package/dist/src/cli/init.d.ts.map +1 -0
  15. package/dist/src/cli/init.js +366 -0
  16. package/dist/src/cli/init.js.map +1 -0
  17. package/dist/src/cli/lint.d.ts +32 -0
  18. package/dist/src/cli/lint.d.ts.map +1 -0
  19. package/dist/src/cli/lint.js +171 -0
  20. package/dist/src/cli/lint.js.map +1 -0
  21. package/dist/src/cli/report.d.ts +62 -0
  22. package/dist/src/cli/report.d.ts.map +1 -0
  23. package/dist/src/cli/report.js +188 -0
  24. package/dist/src/cli/report.js.map +1 -0
  25. package/dist/src/cli/upgrade.d.ts +35 -0
  26. package/dist/src/cli/upgrade.d.ts.map +1 -0
  27. package/dist/src/cli/upgrade.js +126 -0
  28. package/dist/src/cli/upgrade.js.map +1 -0
  29. package/dist/src/core/config.d.ts +60 -8
  30. package/dist/src/core/config.d.ts.map +1 -1
  31. package/dist/src/core/config.js +31 -1
  32. package/dist/src/core/config.js.map +1 -1
  33. package/dist/src/core/notify.d.ts +41 -0
  34. package/dist/src/core/notify.d.ts.map +1 -0
  35. package/dist/src/core/notify.js +153 -0
  36. package/dist/src/core/notify.js.map +1 -0
  37. package/dist/src/core/run-logger.d.ts +83 -0
  38. package/dist/src/core/run-logger.d.ts.map +1 -0
  39. package/dist/src/core/run-logger.js +70 -0
  40. package/dist/src/core/run-logger.js.map +1 -0
  41. package/dist/src/mcp/server.d.ts +9 -0
  42. package/dist/src/mcp/server.d.ts.map +1 -1
  43. package/dist/src/mcp/server.js +148 -1
  44. package/dist/src/mcp/server.js.map +1 -1
  45. package/dist/src/providers/gemini.d.ts +29 -0
  46. package/dist/src/providers/gemini.d.ts.map +1 -0
  47. package/dist/src/providers/gemini.js +47 -0
  48. package/dist/src/providers/gemini.js.map +1 -0
  49. package/dist/src/providers/index.d.ts +2 -0
  50. package/dist/src/providers/index.d.ts.map +1 -1
  51. package/dist/src/providers/index.js +8 -0
  52. package/dist/src/providers/index.js.map +1 -1
  53. package/dist/src/providers/openrouter.d.ts +25 -0
  54. package/dist/src/providers/openrouter.d.ts.map +1 -0
  55. package/dist/src/providers/openrouter.js +43 -0
  56. package/dist/src/providers/openrouter.js.map +1 -0
  57. package/dist/src/tools/registry.d.ts +5 -3
  58. package/dist/src/tools/registry.d.ts.map +1 -1
  59. package/dist/src/tools/registry.js +35 -12
  60. package/dist/src/tools/registry.js.map +1 -1
  61. package/package.json +1 -1
@@ -0,0 +1,171 @@
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 { execFile } from "node:child_process";
12
+ import { existsSync, readFileSync } from "node:fs";
13
+ import { resolve } from "node:path";
14
+ import chalk from "chalk";
15
+ import { parseSpec } from "../core/spec-parser.js";
16
+ // ---------------------------------------------------------------------------
17
+ // Constants
18
+ // ---------------------------------------------------------------------------
19
+ /** Specs with more sub-tasks than this threshold get a large-spec warning. */
20
+ const MAX_SUBTASKS_WARNING = 8;
21
+ // ---------------------------------------------------------------------------
22
+ // Pure validation (exported for testing)
23
+ // ---------------------------------------------------------------------------
24
+ /**
25
+ * Validate a parsed Spec for structural completeness.
26
+ * Returns all issues found — does not fail fast.
27
+ */
28
+ export function lintSpec(spec) {
29
+ const issues = [];
30
+ // ── Errors (blocking — goal executor will fail or produce wrong results) ──
31
+ if (!spec.title || spec.title === "Untitled Spec") {
32
+ issues.push({
33
+ severity: "error",
34
+ message: "spec has no title",
35
+ fix: "Add a # Title line at the top of the spec file",
36
+ });
37
+ }
38
+ if (!spec.problemStatement || spec.problemStatement.trim() === "") {
39
+ issues.push({
40
+ severity: "error",
41
+ message: "## Problem Statement section is empty",
42
+ fix: "Describe what problem this spec solves",
43
+ });
44
+ }
45
+ if (spec.decomposition.length === 0) {
46
+ issues.push({
47
+ severity: "error",
48
+ message: "## Decomposition section has no sub-tasks",
49
+ fix: "Add at least one sub-task with ### Name, Input, Output, and Success Criteria",
50
+ });
51
+ }
52
+ if (spec.acceptanceCriteria.length === 0) {
53
+ issues.push({
54
+ severity: "error",
55
+ message: "## Acceptance Criteria section is empty",
56
+ fix: "Add at least one criterion — the goal executor checks these to determine success",
57
+ });
58
+ }
59
+ // ── Warnings (advisory — goal executor will run but may produce poor results) ──
60
+ if (spec.evalCommand === "npm test") {
61
+ issues.push({
62
+ severity: "warn",
63
+ message: 'evalCommand is "npm test" (the default) — confirm this is correct for your project',
64
+ fix: 'Add "eval: <your-test-command>" to the spec if your project uses a different test runner',
65
+ });
66
+ }
67
+ if (spec.decomposition.length > MAX_SUBTASKS_WARNING) {
68
+ issues.push({
69
+ severity: "warn",
70
+ message: `spec has ${spec.decomposition.length} sub-tasks — large specs are unreliable`,
71
+ fix: "Consider breaking this into multiple smaller specs (2-3 sub-tasks each) and running them sequentially",
72
+ });
73
+ }
74
+ for (const subtask of spec.decomposition) {
75
+ if (!subtask.successCriteria || subtask.successCriteria.trim() === "") {
76
+ issues.push({
77
+ severity: "warn",
78
+ message: `sub-task "${subtask.name || "<unnamed>"}" has no Success Criteria`,
79
+ fix: "Add a Success Criteria line so satori knows when the sub-task is done",
80
+ });
81
+ }
82
+ if (!subtask.name || subtask.name.trim() === "") {
83
+ issues.push({
84
+ severity: "warn",
85
+ message: "a sub-task has an empty name",
86
+ fix: "Give each sub-task a descriptive name under ### heading",
87
+ });
88
+ }
89
+ }
90
+ const errorCount = issues.filter((i) => i.severity === "error").length;
91
+ return { ok: errorCount === 0, issues };
92
+ }
93
+ // ---------------------------------------------------------------------------
94
+ // runLint — entry point
95
+ // ---------------------------------------------------------------------------
96
+ /**
97
+ * Read a spec file, parse it, run lintSpec(), and print results.
98
+ * Returns true if there are no errors (may have warnings).
99
+ * Returns false if any errors were found.
100
+ */
101
+ export async function runLint(specFilePath) {
102
+ const absPath = resolve(specFilePath);
103
+ if (!existsSync(absPath)) {
104
+ console.error(chalk.red(` ✗ File not found: ${specFilePath}`));
105
+ return false;
106
+ }
107
+ let content;
108
+ try {
109
+ content = readFileSync(absPath, "utf8");
110
+ }
111
+ catch (err) {
112
+ console.error(chalk.red(` ✗ Could not read ${specFilePath}: ${err.message}`));
113
+ return false;
114
+ }
115
+ const spec = parseSpec(content);
116
+ const result = lintSpec(spec);
117
+ // Check evalCommand's first word is on PATH.
118
+ // Skip when evalCommand equals the default ("npm test") — most dev machines
119
+ // have npm, and the existing "npm test (default)" warning already covers that.
120
+ // Guard against empty evalBin (parseSpec always returns a default, but be safe).
121
+ const evalBin = spec.evalCommand.split(/\s+/)[0];
122
+ if (evalBin && spec.evalCommand !== "npm test") {
123
+ const binOnPath = await new Promise((resolve) => {
124
+ execFile("which", [evalBin], { shell: false }, (err) => resolve(!err));
125
+ });
126
+ if (!binOnPath) {
127
+ result.issues.push({
128
+ severity: "warn",
129
+ message: `evalCommand uses "${evalBin}" which was not found on PATH (note: shell virtualenv activation is not applied here — run \`which ${evalBin}\` in your shell to verify)`,
130
+ fix: `Install ${evalBin} or update evalCommand to a command that is available on PATH`,
131
+ });
132
+ // ok is unchanged — PATH warnings are advisory only, not blocking errors
133
+ }
134
+ }
135
+ const errors = result.issues.filter((i) => i.severity === "error");
136
+ const warns = result.issues.filter((i) => i.severity === "warn");
137
+ console.log(chalk.bold(`\n Linting ${specFilePath}\n`));
138
+ console.log(chalk.dim(` Title: ${spec.title}`));
139
+ console.log(chalk.dim(` Sub-tasks: ${spec.decomposition.length}`));
140
+ console.log(chalk.dim(` Criteria: ${spec.acceptanceCriteria.length}`));
141
+ console.log(chalk.dim(` Eval: ${spec.evalCommand}`));
142
+ console.log("");
143
+ if (result.issues.length === 0) {
144
+ console.log(chalk.green(" ✓ Spec looks good. Ready to run phase2s goal."));
145
+ console.log("");
146
+ return true;
147
+ }
148
+ for (const issue of result.issues) {
149
+ if (issue.severity === "error") {
150
+ console.log(chalk.red(` ✗ ${issue.message}`));
151
+ }
152
+ else {
153
+ console.log(chalk.yellow(` ⚠ ${issue.message}`));
154
+ }
155
+ if (issue.fix) {
156
+ console.log(chalk.dim(` ${issue.fix}`));
157
+ }
158
+ }
159
+ console.log("");
160
+ if (errors.length > 0) {
161
+ const plural = errors.length === 1 ? "error" : "errors";
162
+ const warnNote = warns.length > 0 ? `, ${warns.length} warning${warns.length === 1 ? "" : "s"}` : "";
163
+ console.log(chalk.red(` ${errors.length} ${plural}${warnNote} found. Fix errors before running phase2s goal.`));
164
+ }
165
+ else {
166
+ console.log(chalk.yellow(` ${warns.length} warning${warns.length === 1 ? "" : "s"} — spec is runnable but review the notes above.`));
167
+ }
168
+ console.log("");
169
+ return result.ok;
170
+ }
171
+ //# 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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"}