myshell-tools 1.0.0 → 2.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/CHANGELOG.md +44 -69
  2. package/LICENSE +21 -21
  3. package/README.md +178 -318
  4. package/dist/cli.d.ts +8 -0
  5. package/dist/cli.js +106 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/cost.d.ts +36 -0
  8. package/dist/commands/cost.js +103 -0
  9. package/dist/commands/cost.js.map +1 -0
  10. package/dist/commands/doctor.d.ts +36 -0
  11. package/dist/commands/doctor.js +115 -0
  12. package/dist/commands/doctor.js.map +1 -0
  13. package/dist/core/assess.d.ts +25 -0
  14. package/dist/core/assess.js +142 -0
  15. package/dist/core/assess.js.map +1 -0
  16. package/dist/core/classify.d.ts +19 -0
  17. package/dist/core/classify.js +80 -0
  18. package/dist/core/classify.js.map +1 -0
  19. package/dist/core/escalate.d.ts +32 -0
  20. package/dist/core/escalate.js +57 -0
  21. package/dist/core/escalate.js.map +1 -0
  22. package/dist/core/index.d.ts +13 -0
  23. package/dist/core/index.js +12 -0
  24. package/dist/core/index.js.map +1 -0
  25. package/dist/core/orchestrate.d.ts +42 -0
  26. package/dist/core/orchestrate.js +439 -0
  27. package/dist/core/orchestrate.js.map +1 -0
  28. package/dist/core/policy.d.ts +9 -0
  29. package/dist/core/policy.js +27 -0
  30. package/dist/core/policy.js.map +1 -0
  31. package/dist/core/prompt.d.ts +26 -0
  32. package/dist/core/prompt.js +125 -0
  33. package/dist/core/prompt.js.map +1 -0
  34. package/dist/core/review.d.ts +46 -0
  35. package/dist/core/review.js +148 -0
  36. package/dist/core/review.js.map +1 -0
  37. package/dist/core/route.d.ts +28 -0
  38. package/dist/core/route.js +52 -0
  39. package/dist/core/route.js.map +1 -0
  40. package/dist/core/types.d.ts +141 -0
  41. package/dist/core/types.js +14 -0
  42. package/dist/core/types.js.map +1 -0
  43. package/dist/infra/atomic.d.ts +53 -0
  44. package/dist/infra/atomic.js +171 -0
  45. package/dist/infra/atomic.js.map +1 -0
  46. package/dist/infra/clock.d.ts +9 -0
  47. package/dist/infra/clock.js +15 -0
  48. package/dist/infra/clock.js.map +1 -0
  49. package/dist/infra/index.d.ts +9 -0
  50. package/dist/infra/index.js +7 -0
  51. package/dist/infra/index.js.map +1 -0
  52. package/dist/infra/ledger.d.ts +49 -0
  53. package/dist/infra/ledger.js +90 -0
  54. package/dist/infra/ledger.js.map +1 -0
  55. package/dist/infra/paths.d.ts +28 -0
  56. package/dist/infra/paths.js +38 -0
  57. package/dist/infra/paths.js.map +1 -0
  58. package/dist/infra/pricing.d.ts +47 -0
  59. package/dist/infra/pricing.js +151 -0
  60. package/dist/infra/pricing.js.map +1 -0
  61. package/dist/infra/session.d.ts +28 -0
  62. package/dist/infra/session.js +61 -0
  63. package/dist/infra/session.js.map +1 -0
  64. package/dist/interface/render.d.ts +27 -0
  65. package/dist/interface/render.js +134 -0
  66. package/dist/interface/render.js.map +1 -0
  67. package/dist/interface/repl.d.ts +23 -0
  68. package/dist/interface/repl.js +90 -0
  69. package/dist/interface/repl.js.map +1 -0
  70. package/dist/interface/run.d.ts +20 -0
  71. package/dist/interface/run.js +31 -0
  72. package/dist/interface/run.js.map +1 -0
  73. package/dist/providers/claude-parse.d.ts +24 -0
  74. package/dist/providers/claude-parse.js +113 -0
  75. package/dist/providers/claude-parse.js.map +1 -0
  76. package/dist/providers/claude.d.ts +45 -0
  77. package/dist/providers/claude.js +122 -0
  78. package/dist/providers/claude.js.map +1 -0
  79. package/dist/providers/codex-parse.d.ts +32 -0
  80. package/dist/providers/codex-parse.js +145 -0
  81. package/dist/providers/codex-parse.js.map +1 -0
  82. package/dist/providers/codex.d.ts +44 -0
  83. package/dist/providers/codex.js +124 -0
  84. package/dist/providers/codex.js.map +1 -0
  85. package/dist/providers/detect.d.ts +49 -0
  86. package/dist/providers/detect.js +125 -0
  87. package/dist/providers/detect.js.map +1 -0
  88. package/dist/providers/errors.d.ts +49 -0
  89. package/dist/providers/errors.js +189 -0
  90. package/dist/providers/errors.js.map +1 -0
  91. package/dist/providers/index.d.ts +9 -0
  92. package/dist/providers/index.js +7 -0
  93. package/dist/providers/index.js.map +1 -0
  94. package/dist/providers/port.d.ts +74 -0
  95. package/dist/providers/port.js +16 -0
  96. package/dist/providers/port.js.map +1 -0
  97. package/dist/providers/registry.d.ts +21 -0
  98. package/dist/providers/registry.js +34 -0
  99. package/dist/providers/registry.js.map +1 -0
  100. package/dist/ui/banner.d.ts +19 -0
  101. package/dist/ui/banner.js +32 -0
  102. package/dist/ui/banner.js.map +1 -0
  103. package/dist/ui/spinner.d.ts +27 -0
  104. package/dist/ui/spinner.js +67 -0
  105. package/dist/ui/spinner.js.map +1 -0
  106. package/dist/ui/theme.d.ts +32 -0
  107. package/dist/ui/theme.js +56 -0
  108. package/dist/ui/theme.js.map +1 -0
  109. package/package.json +55 -49
  110. package/data/orchestrator.json +0 -113
  111. package/src/auth/recovery.mjs +0 -328
  112. package/src/auth/refresh.mjs +0 -373
  113. package/src/chef.mjs +0 -348
  114. package/src/cli/doctor.mjs +0 -568
  115. package/src/cli/reset.mjs +0 -447
  116. package/src/cli/status.mjs +0 -379
  117. package/src/cli.mjs +0 -429
  118. package/src/commands/doctor.mjs +0 -375
  119. package/src/commands/help.mjs +0 -324
  120. package/src/commands/status.mjs +0 -331
  121. package/src/monitor/health.mjs +0 -486
  122. package/src/monitor/performance.mjs +0 -442
  123. package/src/monitor/report.mjs +0 -535
  124. package/src/orchestrator/classify.mjs +0 -391
  125. package/src/orchestrator/confidence.mjs +0 -151
  126. package/src/orchestrator/handoffs.mjs +0 -231
  127. package/src/orchestrator/review.mjs +0 -222
  128. package/src/providers/balance.mjs +0 -201
  129. package/src/providers/claude.mjs +0 -236
  130. package/src/providers/codex.mjs +0 -255
  131. package/src/providers/detect.mjs +0 -185
  132. package/src/providers/errors.mjs +0 -373
  133. package/src/providers/select.mjs +0 -162
  134. package/src/repl-enhanced.mjs +0 -417
  135. package/src/repl.mjs +0 -321
  136. package/src/state/archive.mjs +0 -366
  137. package/src/state/atomic.mjs +0 -116
  138. package/src/state/cleanup.mjs +0 -440
  139. package/src/state/recovery.mjs +0 -461
  140. package/src/state/session.mjs +0 -147
  141. package/src/ui/errors.mjs +0 -456
  142. package/src/ui/formatter.mjs +0 -327
  143. package/src/ui/icons.mjs +0 -318
  144. package/src/ui/progress.mjs +0 -468
  145. package/templates/prompts/confidence-format.txt +0 -14
  146. package/templates/prompts/ic-with-feedback.txt +0 -41
  147. package/templates/prompts/ic.txt +0 -13
  148. package/templates/prompts/manager-review.txt +0 -40
  149. package/templates/prompts/manager.txt +0 -14
  150. package/templates/prompts/worker.txt +0 -12
@@ -0,0 +1,103 @@
1
+ /**
2
+ * src/commands/cost.ts — `myshell-tools cost` spend-summary command.
3
+ *
4
+ * Reads the on-disk ledger, computes real totals, and prints an honest
5
+ * per-model breakdown together with a counterfactual "what if you had used
6
+ * the manager-tier flagship for every call?" comparison.
7
+ *
8
+ * Counterfactual (apples-to-apples)
9
+ * ---------------------------------
10
+ * The "Billed total" is the real amount from the ledger (includes caching and
11
+ * discounts). The counterfactual compares ONLY like-for-like list prices:
12
+ * routed = sum over entries of listPrice(entry.model) × entry tokens
13
+ * flagship = sum over entries of listPrice(manager flagship) × entry tokens
14
+ * multiplier = flagship / routed
15
+ * We never compare the cache-adjusted billed total against a list-price flagship
16
+ * estimate — that would understate flagship and mislead.
17
+ *
18
+ * Honesty contract: every figure comes from the real ledger; no values are
19
+ * fabricated. If the ledger is empty the command says so and returns 0.
20
+ */
21
+ import { readLedger, summarizeLedger } from '../infra/ledger.js';
22
+ import { getCheapestForTier, calculateCost, getModelPricing } from '../infra/pricing.js';
23
+ import { bold, dim, cyan, divider, label } from '../ui/theme.js';
24
+ // ---------------------------------------------------------------------------
25
+ // Pure report builder — testable with hand-built LedgerEntry[]
26
+ // ---------------------------------------------------------------------------
27
+ /**
28
+ * Build the cost-report lines from a (possibly empty) ledger entry array.
29
+ *
30
+ * Pure function: no I/O, no process.exit, no Date/Math.random.
31
+ * Called by runCost after reading the real ledger, and by unit tests.
32
+ */
33
+ export function formatCostReport(entries, color = false) {
34
+ if (entries.length === 0) {
35
+ return ['No usage recorded yet — run a task first.'];
36
+ }
37
+ const summary = summarizeLedger(entries);
38
+ const flagship = getCheapestForTier('manager');
39
+ // Apples-to-apples: price BOTH the models actually used AND the flagship on the
40
+ // SAME basis (list price × tokens). We do NOT compare the billed total (which
41
+ // includes caching/discounts) against a list-price flagship estimate.
42
+ let routedListUsd = 0;
43
+ let flagshipListUsd = 0;
44
+ for (const entry of entries) {
45
+ const entryPricing = getModelPricing(entry.provider, entry.model);
46
+ if (entryPricing) {
47
+ routedListUsd += calculateCost(entry.inputTokens, entry.outputTokens, entryPricing);
48
+ }
49
+ flagshipListUsd += calculateCost(entry.inputTokens, entry.outputTokens, flagship);
50
+ }
51
+ const lines = [];
52
+ lines.push(bold('myshell-tools cost', color));
53
+ lines.push(divider(color));
54
+ // ---- Summary ---------------------------------------------------------------
55
+ lines.push(`${label('Billed total', color)}: $${summary.totalUsd.toFixed(4)} ` +
56
+ `${dim('(as billed, incl. caching/discounts)', color)}`);
57
+ lines.push(`${label('Total calls', color)}: ${summary.calls}`);
58
+ lines.push(divider(color));
59
+ // ---- Per-model breakdown ---------------------------------------------------
60
+ lines.push(bold('Per-model breakdown', color));
61
+ for (const [model, ms] of Object.entries(summary.byModel)) {
62
+ const callStr = ms.calls === 1 ? '1 call' : `${ms.calls} calls`;
63
+ lines.push(` ${cyan(model, color)}: ${callStr}, $${ms.usd.toFixed(4)}`);
64
+ }
65
+ lines.push(divider(color));
66
+ // ---- Counterfactual (list-price, token-for-token, apples-to-apples) --------
67
+ const flagshipLabel = `${flagship.model} (${flagship.tier} tier)`;
68
+ lines.push(bold('Counterfactual — list price, token-for-token', color));
69
+ lines.push(`${dim('Flagship model', color)}: ${flagshipLabel}` +
70
+ ` ${dim(`($${flagship.inputPer1M}/M in, $${flagship.outputPer1M}/M out)`, color)}`);
71
+ lines.push(`${label('Routed (models used)', color)}: $${routedListUsd.toFixed(4)}`);
72
+ lines.push(`${label('Always-flagship', color)}: $${flagshipListUsd.toFixed(4)}`);
73
+ if (routedListUsd > 0 && flagshipListUsd > routedListUsd) {
74
+ const multiplier = flagshipListUsd / routedListUsd;
75
+ lines.push(`Routing saved you money: always-flagship would cost ${multiplier.toFixed(1)}x more ` +
76
+ `($${flagshipListUsd.toFixed(4)} vs $${routedListUsd.toFixed(4)} at list price).`);
77
+ }
78
+ else if (routedListUsd > 0) {
79
+ lines.push('Every call already used a flagship-tier (or pricier) model — no routing savings to show.');
80
+ }
81
+ else {
82
+ lines.push(dim('No priced models in the ledger — cannot compute a list-price comparison.', color));
83
+ }
84
+ return lines;
85
+ }
86
+ // ---------------------------------------------------------------------------
87
+ // I/O runner — called by cli.ts
88
+ // ---------------------------------------------------------------------------
89
+ /**
90
+ * Read the ledger for `cwd`, build the cost report, and write it to `out`.
91
+ *
92
+ * Returns 0 always (cost reporting is informational, not an error condition).
93
+ * Never calls process.exit — that is handled exclusively by src/cli.ts.
94
+ */
95
+ export async function runCost(cwd, out) {
96
+ const entries = await readLedger(cwd);
97
+ const lines = formatCostReport(entries, out.color);
98
+ for (const line of lines) {
99
+ out.write(line + '\n');
100
+ }
101
+ return 0;
102
+ }
103
+ //# sourceMappingURL=cost.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost.js","sourceRoot":"","sources":["../../src/commands/cost.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * src/commands/doctor.ts — `myshell-tools doctor` health-check command.
3
+ *
4
+ * Probes the runtime environment (providers, filesystem, pricing) and prints
5
+ * an honest, human-readable report to an OutputSink. All displayed data comes
6
+ * from real detection results — no fabricated values.
7
+ *
8
+ * Honesty contract: authentication status for Claude is labelled OPTIMISTIC
9
+ * because we cannot cheaply probe auth state without spending API quota.
10
+ */
11
+ import type { OutputSink } from '../interface/render.js';
12
+ import type { EnvironmentStatus } from '../providers/detect.js';
13
+ export interface DoctorExtras {
14
+ readonly nodeVersion: string;
15
+ readonly stateWritable: boolean;
16
+ readonly pricingStale: boolean;
17
+ }
18
+ /**
19
+ * Build the doctor report lines from pre-collected data.
20
+ *
21
+ * Pure function: no I/O, no process.exit, no side effects.
22
+ * Used by runDoctor after it collects the real data, and by unit tests with
23
+ * hand-built inputs.
24
+ *
25
+ * @param env - Full environment status (from detectEnvironment or a fake).
26
+ * @param extras - Supplemental runtime info (node version, write probe, etc.).
27
+ * @param color - Whether to emit ANSI colour codes.
28
+ */
29
+ export declare function buildDoctorReport(env: EnvironmentStatus, extras: DoctorExtras, color: boolean): string[];
30
+ /**
31
+ * Detect the environment, probe I/O, build the report, and write it to `out`.
32
+ *
33
+ * Returns 0 when at least one provider is installed, 1 otherwise.
34
+ * Never calls process.exit — that is handled exclusively by src/cli.ts.
35
+ */
36
+ export declare function runDoctor(out: OutputSink): Promise<number>;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * src/commands/doctor.ts — `myshell-tools doctor` health-check command.
3
+ *
4
+ * Probes the runtime environment (providers, filesystem, pricing) and prints
5
+ * an honest, human-readable report to an OutputSink. All displayed data comes
6
+ * from real detection results — no fabricated values.
7
+ *
8
+ * Honesty contract: authentication status for Claude is labelled OPTIMISTIC
9
+ * because we cannot cheaply probe auth state without spending API quota.
10
+ */
11
+ import { access, mkdir, writeFile, rm } from 'node:fs/promises';
12
+ import { join } from 'node:path';
13
+ import { detectEnvironment, getInstallCommand } from '../providers/detect.js';
14
+ import { isPricingStale } from '../infra/pricing.js';
15
+ import { bold, green, red, yellow, dim, divider, label } from '../ui/theme.js';
16
+ /**
17
+ * Build the doctor report lines from pre-collected data.
18
+ *
19
+ * Pure function: no I/O, no process.exit, no side effects.
20
+ * Used by runDoctor after it collects the real data, and by unit tests with
21
+ * hand-built inputs.
22
+ *
23
+ * @param env - Full environment status (from detectEnvironment or a fake).
24
+ * @param extras - Supplemental runtime info (node version, write probe, etc.).
25
+ * @param color - Whether to emit ANSI colour codes.
26
+ */
27
+ export function buildDoctorReport(env, extras, color) {
28
+ const lines = [];
29
+ lines.push(bold('myshell-tools doctor', color));
30
+ lines.push(divider(color));
31
+ // ---- Platform & Node -------------------------------------------------------
32
+ lines.push(`${label('Platform', color)}: ${env.platform}`);
33
+ lines.push(`${label('Node', color)}: ${extras.nodeVersion}`);
34
+ // ---- .myshell-tools writability ---------------------------------------------------
35
+ const writableText = extras.stateWritable
36
+ ? green('writable', color)
37
+ : red('not writable', color);
38
+ lines.push(`${label('.myshell-tools dir', color)}: ${writableText}`);
39
+ // ---- Pricing staleness -----------------------------------------------------
40
+ const pricingText = extras.pricingStale
41
+ ? yellow('stale — consider updating myshell-tools', color)
42
+ : green('up to date', color);
43
+ lines.push(`${label('Pricing table', color)}: ${pricingText}`);
44
+ lines.push(divider(color));
45
+ // ---- Providers -------------------------------------------------------------
46
+ lines.push(bold('Providers', color));
47
+ for (const ps of [env.claude, env.codex]) {
48
+ if (ps.installed) {
49
+ const versionStr = ps.version !== null ? ps.version : 'unknown';
50
+ lines.push(` ${green('✓', color)} ${bold(ps.id, color)} — installed, version: ${versionStr}`);
51
+ // Auth is optimistic for Claude; codex is always not-installed for now
52
+ if (ps.id === 'claude') {
53
+ lines.push(` ${label('auth', color)}: ${dim('assumed; verified on first run', color)}`);
54
+ }
55
+ }
56
+ else {
57
+ lines.push(` ${red('✗', color)} ${bold(ps.id, color)} — ${red('not installed', color)}`);
58
+ lines.push(` ${dim(`Install: ${getInstallCommand(ps.id)}`, color)}`);
59
+ }
60
+ }
61
+ lines.push(divider(color));
62
+ // ---- Overall status --------------------------------------------------------
63
+ if (env.hasAnyProvider) {
64
+ lines.push(green('Ready — at least one provider is available.', color));
65
+ }
66
+ else {
67
+ lines.push(red('No providers found.', color) +
68
+ ' Install claude or codex to use myshell-tools.');
69
+ }
70
+ return lines;
71
+ }
72
+ // ---------------------------------------------------------------------------
73
+ // I/O runner — called by cli.ts
74
+ // ---------------------------------------------------------------------------
75
+ /**
76
+ * Probe the .myshell-tools directory for writability.
77
+ *
78
+ * Creates .myshell-tools/ if needed, writes a temp file, then removes it.
79
+ * Returns true when successful, false on any I/O error.
80
+ */
81
+ async function probestateWritable(cwd) {
82
+ const stateDir = join(cwd, '.myshell-tools');
83
+ const probe = join(stateDir, '.doctor-probe');
84
+ try {
85
+ await mkdir(stateDir, { recursive: true });
86
+ await writeFile(probe, '');
87
+ await rm(probe, { force: true });
88
+ await access(stateDir);
89
+ return true;
90
+ }
91
+ catch {
92
+ return false;
93
+ }
94
+ }
95
+ /**
96
+ * Detect the environment, probe I/O, build the report, and write it to `out`.
97
+ *
98
+ * Returns 0 when at least one provider is installed, 1 otherwise.
99
+ * Never calls process.exit — that is handled exclusively by src/cli.ts.
100
+ */
101
+ export async function runDoctor(out) {
102
+ const env = await detectEnvironment();
103
+ const stateWritable = await probestateWritable(process.cwd());
104
+ const extras = {
105
+ nodeVersion: process.version,
106
+ stateWritable,
107
+ pricingStale: isPricingStale(),
108
+ };
109
+ const lines = buildDoctorReport(env, extras, out.color);
110
+ for (const line of lines) {
111
+ out.write(line + '\n');
112
+ }
113
+ return env.hasAnyProvider ? 0 : 1;
114
+ }
115
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * src/core/assess.ts — parse real confidence signals from model output.
3
+ *
4
+ * The model is instructed (via prompt.ts) to end its response with a JSON
5
+ * envelope on its own line:
6
+ * {"confidence": 0.0-1.0, "escalate": true|false, "reason": "...", "needs_review": true|false}
7
+ *
8
+ * Honesty Contract: if the envelope is absent, truncated, or malformed we
9
+ * return confidence=null. We NEVER guess or fabricate a confidence number from
10
+ * keywords or heuristics (that was the v1 sin). This function must never throw
11
+ * on any input — it degrades gracefully on all malformed/garbage data.
12
+ *
13
+ * Pure module: no I/O, no time, no randomness.
14
+ */
15
+ import type { Assessment } from './types.js';
16
+ /**
17
+ * Parse a trailing JSON confidence envelope from `output`.
18
+ *
19
+ * Returns real parsed values when the envelope is present and valid.
20
+ * Returns `{ confidence: null, escalate: false, reason: 'no confidence envelope', needsReview: false }`
21
+ * when the envelope is absent or unparseable.
22
+ *
23
+ * @param output - The full text output from the model.
24
+ */
25
+ export declare function assess(output: string): Assessment;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * src/core/assess.ts — parse real confidence signals from model output.
3
+ *
4
+ * The model is instructed (via prompt.ts) to end its response with a JSON
5
+ * envelope on its own line:
6
+ * {"confidence": 0.0-1.0, "escalate": true|false, "reason": "...", "needs_review": true|false}
7
+ *
8
+ * Honesty Contract: if the envelope is absent, truncated, or malformed we
9
+ * return confidence=null. We NEVER guess or fabricate a confidence number from
10
+ * keywords or heuristics (that was the v1 sin). This function must never throw
11
+ * on any input — it degrades gracefully on all malformed/garbage data.
12
+ *
13
+ * Pure module: no I/O, no time, no randomness.
14
+ */
15
+ // ---------------------------------------------------------------------------
16
+ // Helpers
17
+ // ---------------------------------------------------------------------------
18
+ /**
19
+ * Attempt to extract the last JSON object from `text` that looks like a
20
+ * confidence envelope. Returns the raw parsed object or null.
21
+ *
22
+ * Strategy: scan backwards for the last `{...}` block that contains at least
23
+ * the `confidence` key, then try to parse it. This is robust to:
24
+ * - trailing whitespace / newlines
25
+ * - the model emitting extra text after the envelope (we pick the LAST match)
26
+ * - duplicate envelopes (last one wins — the model may have regenerated it)
27
+ * - garbage / truncated JSON elsewhere in the output
28
+ */
29
+ function extractEnvelope(text) {
30
+ // Find all candidates: substrings that start with '{' and end with '}'
31
+ // We do this by iterating over all '{' positions and trying to match the
32
+ // closing '}', handling depth. We collect all valid JSON objects and
33
+ // return the last one that has a 'confidence' key.
34
+ const candidates = [];
35
+ let i = 0;
36
+ while (i < text.length) {
37
+ const start = text.indexOf('{', i);
38
+ if (start === -1)
39
+ break;
40
+ // Walk forward tracking brace depth to find the matching '}'
41
+ let depth = 0;
42
+ let j = start;
43
+ let foundClose = false;
44
+ while (j < text.length) {
45
+ if (text[j] === '{') {
46
+ depth++;
47
+ }
48
+ else if (text[j] === '}') {
49
+ depth--;
50
+ if (depth === 0) {
51
+ foundClose = true;
52
+ break;
53
+ }
54
+ }
55
+ j++;
56
+ }
57
+ if (foundClose) {
58
+ const candidate = text.slice(start, j + 1);
59
+ try {
60
+ const parsed = JSON.parse(candidate);
61
+ if (parsed !== null &&
62
+ typeof parsed === 'object' &&
63
+ !Array.isArray(parsed) &&
64
+ 'confidence' in parsed) {
65
+ candidates.push(parsed);
66
+ }
67
+ }
68
+ catch {
69
+ // Not valid JSON — skip
70
+ }
71
+ }
72
+ i = start + 1;
73
+ }
74
+ if (candidates.length === 0)
75
+ return null;
76
+ // Return the last valid envelope (handles duplicate/regenerated envelopes)
77
+ return candidates[candidates.length - 1] ?? null;
78
+ }
79
+ /**
80
+ * Clamp a number to [0, 1].
81
+ */
82
+ function clamp01(n) {
83
+ return Math.min(1, Math.max(0, n));
84
+ }
85
+ /**
86
+ * Coerce an unknown value to boolean — any truthy value maps to true.
87
+ */
88
+ function coerceBool(v) {
89
+ if (typeof v === 'boolean')
90
+ return v;
91
+ if (typeof v === 'number')
92
+ return v !== 0;
93
+ if (typeof v === 'string')
94
+ return v === 'true' || v === '1';
95
+ return false;
96
+ }
97
+ // ---------------------------------------------------------------------------
98
+ // Public API
99
+ // ---------------------------------------------------------------------------
100
+ /**
101
+ * Parse a trailing JSON confidence envelope from `output`.
102
+ *
103
+ * Returns real parsed values when the envelope is present and valid.
104
+ * Returns `{ confidence: null, escalate: false, reason: 'no confidence envelope', needsReview: false }`
105
+ * when the envelope is absent or unparseable.
106
+ *
107
+ * @param output - The full text output from the model.
108
+ */
109
+ export function assess(output) {
110
+ const NULL_RESULT = {
111
+ confidence: null,
112
+ escalate: false,
113
+ reason: 'no confidence envelope',
114
+ needsReview: false,
115
+ };
116
+ // Guard: never throw on any input
117
+ if (typeof output !== 'string' || output.length === 0) {
118
+ return NULL_RESULT;
119
+ }
120
+ let envelope;
121
+ try {
122
+ envelope = extractEnvelope(output);
123
+ }
124
+ catch {
125
+ return NULL_RESULT;
126
+ }
127
+ if (envelope === null) {
128
+ return NULL_RESULT;
129
+ }
130
+ // Validate confidence: must be a finite number
131
+ if (typeof envelope.confidence !== 'number' || !isFinite(envelope.confidence)) {
132
+ return NULL_RESULT;
133
+ }
134
+ const confidence = clamp01(envelope.confidence);
135
+ const escalate = coerceBool(envelope.escalate);
136
+ const needsReview = coerceBool(envelope.needs_review);
137
+ const reason = typeof envelope.reason === 'string' && envelope.reason.trim().length > 0
138
+ ? envelope.reason.trim()
139
+ : 'model provided no reason';
140
+ return { confidence, escalate, reason, needsReview };
141
+ }
142
+ //# sourceMappingURL=assess.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assess.js","sourceRoot":"","sources":["../../src/core/assess.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * src/core/classify.ts — pure task classification.
3
+ *
4
+ * Determines the orchestration tier and security risk level from a free-text
5
+ * task description using keyword/regex signals. No I/O, no time, no randomness.
6
+ *
7
+ * Honesty Contract: no fabricated confidence numbers are produced here.
8
+ * The rationale field names the matched signal so callers can audit decisions.
9
+ */
10
+ import type { Classification } from './types.js';
11
+ /**
12
+ * Classify a free-text `task` string into a {@link Classification}.
13
+ *
14
+ * Tier priority: manager > worker > ic (ic is the default).
15
+ * Risk priority: critical > high > medium > low (low is the default).
16
+ *
17
+ * @param task - The raw task description from the user.
18
+ */
19
+ export declare function classify(task: string): Classification;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * src/core/classify.ts — pure task classification.
3
+ *
4
+ * Determines the orchestration tier and security risk level from a free-text
5
+ * task description using keyword/regex signals. No I/O, no time, no randomness.
6
+ *
7
+ * Honesty Contract: no fabricated confidence numbers are produced here.
8
+ * The rationale field names the matched signal so callers can audit decisions.
9
+ */
10
+ // ---------------------------------------------------------------------------
11
+ // Tier signal tables
12
+ // ---------------------------------------------------------------------------
13
+ /** Words that signal a manager-tier task (review, planning, architecture, security). */
14
+ const MANAGER_WORDS = /\b(review|plan|design|architect|audit|security|threat|evaluate|assess|complex)\b/i;
15
+ /** Words that signal a worker-tier task (read-only lookups, searches). */
16
+ const WORKER_WORDS = /\b(find|search|grep|locate|list|read[-\s]only|lookup|scan|what\s+is)\b/i;
17
+ // ---------------------------------------------------------------------------
18
+ // Risk signal tables — highest match wins
19
+ // ---------------------------------------------------------------------------
20
+ /** Critical: auth / secrets / credentials / encryption artefacts. */
21
+ const CRITICAL_RE = /\b(auth|credential|secret|password|token|key|encrypt|certificate)\b|\.env\b/i;
22
+ /** High: payments, deploys, migrations, CI/CD, permissions, schema changes. */
23
+ const HIGH_RE = /\b(login|payment|billing|deploy|migration|ci\/cd|permission|schema)\b/i;
24
+ /** Medium: tests, configs, shared utilities, integrations. */
25
+ const MEDIUM_RE = /\b(test|spec|config|integration|shared|util|lib)\b/i;
26
+ // ---------------------------------------------------------------------------
27
+ // Public API
28
+ // ---------------------------------------------------------------------------
29
+ /**
30
+ * Classify a free-text `task` string into a {@link Classification}.
31
+ *
32
+ * Tier priority: manager > worker > ic (ic is the default).
33
+ * Risk priority: critical > high > medium > low (low is the default).
34
+ *
35
+ * @param task - The raw task description from the user.
36
+ */
37
+ export function classify(task) {
38
+ // --- Tier ---
39
+ let tier;
40
+ let tierSignal;
41
+ if (MANAGER_WORDS.test(task)) {
42
+ tier = 'manager';
43
+ const m = task.match(MANAGER_WORDS);
44
+ tierSignal = m ? `manager keyword '${m[0].toLowerCase()}'` : 'manager keyword';
45
+ }
46
+ else if (WORKER_WORDS.test(task)) {
47
+ tier = 'worker';
48
+ const m = task.match(WORKER_WORDS);
49
+ tierSignal = m ? `worker keyword '${m[0].toLowerCase()}'` : 'worker keyword';
50
+ }
51
+ else {
52
+ tier = 'ic';
53
+ tierSignal = 'no tier keyword matched — defaulting to ic';
54
+ }
55
+ // --- Risk ---
56
+ let risk;
57
+ let riskSignal;
58
+ if (CRITICAL_RE.test(task)) {
59
+ risk = 'critical';
60
+ const m = task.match(CRITICAL_RE);
61
+ riskSignal = `critical keyword '${m ? m[0].toLowerCase() : 'unknown'}'`;
62
+ }
63
+ else if (HIGH_RE.test(task)) {
64
+ risk = 'high';
65
+ const m = task.match(HIGH_RE);
66
+ riskSignal = `high keyword '${m ? m[0].toLowerCase() : 'unknown'}'`;
67
+ }
68
+ else if (MEDIUM_RE.test(task)) {
69
+ risk = 'medium';
70
+ const m = task.match(MEDIUM_RE);
71
+ riskSignal = `medium keyword '${m ? m[0].toLowerCase() : 'unknown'}'`;
72
+ }
73
+ else {
74
+ risk = 'low';
75
+ riskSignal = 'no risk keyword matched — defaulting to low';
76
+ }
77
+ const rationale = `tier: ${tierSignal}; risk: ${riskSignal}`;
78
+ return { tier, risk, rationale };
79
+ }
80
+ //# sourceMappingURL=classify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classify.js","sourceRoot":"","sources":["../../src/core/classify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * src/core/escalate.ts — pure escalation helpers.
3
+ *
4
+ * Provides utilities for determining the next tier in the escalation chain
5
+ * and selecting a cross-vendor reviewer.
6
+ *
7
+ * Pure module: no I/O, no time, no randomness.
8
+ */
9
+ import type { Tier } from './types.js';
10
+ import type { ProviderId } from '../providers/port.js';
11
+ /**
12
+ * Return the next tier up from the given tier, or null if already at the top.
13
+ *
14
+ * Chain: worker → ic → manager → null
15
+ *
16
+ * @param tier - The current orchestration tier.
17
+ */
18
+ export declare function nextTierUp(tier: Tier): Tier | null;
19
+ /**
20
+ * Pick a reviewer provider from the available list, preferring cross-vendor
21
+ * review over same-vendor review.
22
+ *
23
+ * Algorithm:
24
+ * 1. If `available` is empty, return null.
25
+ * 2. Prefer a provider with a DIFFERENT id than `primary` (cross-vendor review).
26
+ * 3. If all available are the same vendor as primary, return `primary` if it
27
+ * is in `available`, else return null.
28
+ *
29
+ * @param available - Provider IDs that are currently reachable.
30
+ * @param primary - The provider ID that ran the IC work being reviewed.
31
+ */
32
+ export declare function pickReviewer(available: ProviderId[], primary: ProviderId): ProviderId | null;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * src/core/escalate.ts — pure escalation helpers.
3
+ *
4
+ * Provides utilities for determining the next tier in the escalation chain
5
+ * and selecting a cross-vendor reviewer.
6
+ *
7
+ * Pure module: no I/O, no time, no randomness.
8
+ */
9
+ // ---------------------------------------------------------------------------
10
+ // Tier chain
11
+ // ---------------------------------------------------------------------------
12
+ /**
13
+ * Return the next tier up from the given tier, or null if already at the top.
14
+ *
15
+ * Chain: worker → ic → manager → null
16
+ *
17
+ * @param tier - The current orchestration tier.
18
+ */
19
+ export function nextTierUp(tier) {
20
+ switch (tier) {
21
+ case 'worker':
22
+ return 'ic';
23
+ case 'ic':
24
+ return 'manager';
25
+ case 'manager':
26
+ return null;
27
+ }
28
+ }
29
+ // ---------------------------------------------------------------------------
30
+ // Reviewer selection
31
+ // ---------------------------------------------------------------------------
32
+ /**
33
+ * Pick a reviewer provider from the available list, preferring cross-vendor
34
+ * review over same-vendor review.
35
+ *
36
+ * Algorithm:
37
+ * 1. If `available` is empty, return null.
38
+ * 2. Prefer a provider with a DIFFERENT id than `primary` (cross-vendor review).
39
+ * 3. If all available are the same vendor as primary, return `primary` if it
40
+ * is in `available`, else return null.
41
+ *
42
+ * @param available - Provider IDs that are currently reachable.
43
+ * @param primary - The provider ID that ran the IC work being reviewed.
44
+ */
45
+ export function pickReviewer(available, primary) {
46
+ if (available.length === 0)
47
+ return null;
48
+ // Prefer a different vendor (cross-vendor review is the goal)
49
+ const crossVendor = available.find((id) => id !== primary);
50
+ if (crossVendor !== undefined)
51
+ return crossVendor;
52
+ // All available are same vendor — return primary if it's in the list
53
+ if (available.includes(primary))
54
+ return primary;
55
+ return null;
56
+ }
57
+ //# sourceMappingURL=escalate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escalate.js","sourceRoot":"","sources":["../../src/core/escalate.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,IAAU;IACnC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,QAAQ;YACX,OAAO,IAAI,CAAC;QACd,KAAK,IAAI;YACP,OAAO,SAAS,CAAC;QACnB,KAAK,SAAS;YACZ,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,qBAAqB;AACrB,8EAA8E;AAE9E;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAAC,SAAuB,EAAE,OAAmB;IACvE,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAExC,8DAA8D;IAC9D,MAAM,WAAW,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC;IAC3D,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,WAAW,CAAC;IAElD,qEAAqE;IACrE,IAAI,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IAEhD,OAAO,IAAI,CAAC;AACd,CAAC"}