@khoavu882/doflow 2.0.0-beta.3 → 2.0.0-beta.5
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/bin/doflow.js +2 -2
- package/core/.antigravity-plugin/plugin.json +0 -1
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
- package/core/harnesses/claude/hooks/session-start.sh +5 -3
- package/core/harnesses/claude/hooks/stop-check.sh +5 -3
- package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
- package/core/harnesses/codex/hooks/session-start.sh +5 -3
- package/core/harnesses/codex/hooks/stop-check.sh +6 -5
- package/core/harnesses/codex/hooks/subagent-audit.sh +17 -6
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +7 -18
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +7 -18
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +7 -19
- package/core/harnesses/gemini/hooks/session-start.sh +5 -3
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +20 -5
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +20 -5
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +20 -5
- package/core/harnesses/kiro/hooks/session-start.sh +11 -5
- package/core/harnesses/kiro/hooks/stop-check.sh +21 -30
- package/core/harnesses/pi/extensions/doflow-guard.js +159 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -11
- package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
- package/core/harnesses/shared/hooks/bridge/bridge.sh +184 -0
- package/core/harnesses/shared/hooks/bridge/envelope.jq +60 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +62 -0
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +4 -2
- package/core/harnesses/shared/hooks/policies/contract.sh +75 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +39 -4
- package/core/harnesses/shared/hooks/policies/lib.sh +4 -2
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +46 -34
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +75 -25
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +67 -73
- package/core/harnesses/shared/hooks/policies/session-context.sh +37 -7
- package/core/harnesses/shared/hooks/policies/stop-check.sh +42 -15
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +35 -5
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +40 -7
- package/core/harnesses/shared/hooks/stream-hook-runner.js +69 -181
- package/core/registry/assets.json +19 -0
- package/core/registry/contracts.json +2 -2
- package/core/registry/harnesses.json +196 -10
- package/core/registry/lifecycle.json +2626 -7
- package/core/registry/storage.json +110 -83
- package/core/registry/verification.json +1 -1
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -2
- package/core/shared/scripts/doflow/bin/doflow-run +10 -6
- package/core/shared/skills/do-design/SKILL.md +6 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +11 -10
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +2 -0
- package/core/shared/skills/do-execute-plan/references/scaffold.md +2 -2
- package/core/shared/skills/do-implement/SKILL.md +3 -2
- package/core/shared/skills/do-plan/SKILL.md +7 -8
- package/package.json +4 -2
- package/src/adapters/antigravity/{index.ts → index.js} +141 -105
- package/src/adapters/claude/{index.ts → index.js} +63 -49
- package/src/adapters/claude/{mcp.ts → mcp.js} +16 -16
- package/src/adapters/codex/{agents.ts → agents.js} +24 -24
- package/src/adapters/codex/{config.ts → config.js} +49 -49
- package/src/adapters/codex/{hooks.ts → hooks.js} +62 -27
- package/src/adapters/codex/{index.ts → index.js} +148 -105
- package/src/adapters/codex/{mcp.ts → mcp.js} +38 -38
- package/src/adapters/codex/{native-projection.ts → native-projection.js} +5 -5
- package/src/adapters/copilot/{index.ts → index.js} +42 -42
- package/src/adapters/{copy-tree.ts → copy-tree.js} +74 -74
- package/src/adapters/gemini/{hooks.ts → hooks.js} +78 -37
- package/src/adapters/gemini/{index.ts → index.js} +96 -57
- package/src/adapters/{hook-commands.ts → hook-commands.js} +8 -8
- package/src/adapters/{index.ts → index.js} +13 -13
- package/src/adapters/kiro/{index.ts → index.js} +49 -35
- package/src/adapters/{mcp-entries.ts → mcp-entries.js} +55 -55
- package/src/adapters/opencode/{index.ts → index.js} +52 -52
- package/src/adapters/pi/hooks.js +119 -0
- package/src/adapters/pi/{index.ts → index.js} +64 -39
- package/src/adapters/pi/{json-members.ts → json-members.js} +23 -23
- package/src/adapters/pi/{mcp.ts → mcp.js} +70 -70
- package/src/adapters/{resolve-source.ts → resolve-source.js} +1 -1
- package/src/cli/commands/{install.ts → install.js} +25 -25
- package/src/cli/commands/{list-backups.ts → list-backups.js} +4 -4
- package/src/cli/commands/{reconcile.ts → reconcile.js} +19 -19
- package/src/cli/commands/{remove.ts → remove.js} +10 -10
- package/src/cli/commands/{rollback.ts → rollback.js} +17 -17
- package/src/cli/commands/{self-update.ts → self-update.js} +3 -3
- package/src/cli/commands/{status.ts → status.js} +24 -24
- package/src/cli/commands/{tools.ts → tools.js} +16 -16
- package/src/cli/commands/{update.ts → update.js} +20 -20
- package/src/cli/{index.ts → index.js} +43 -31
- package/src/cli/{runtime-commands.ts → runtime-commands.js} +97 -58
- package/src/cli/{shared.ts → shared.js} +40 -40
- package/src/helper/{bash-helper.ts → bash-helper.js} +7 -7
- package/src/helper/{git.ts → git.js} +2 -2
- package/src/helper/{guardrails.ts → guardrails.js} +10 -10
- package/src/helper/{harness-paths.ts → harness-paths.js} +24 -24
- package/src/helper/{marker-merge.ts → marker-merge.js} +4 -4
- package/src/helper/{prompt.ts → prompt.js} +5 -5
- package/src/helper/{repo-root.ts → repo-root.js} +2 -2
- package/src/helper/{settings-merge.ts → settings-merge.js} +27 -27
- package/src/helper/{settings-scope.ts → settings-scope.js} +3 -3
- package/src/helper/{toml.ts → toml.js} +48 -48
- package/src/install/{backup-set.ts → backup-set.js} +16 -16
- package/src/install/{backup.ts → backup.js} +65 -65
- package/src/install/{context.ts → context.js} +15 -15
- package/src/install/{manifest.ts → manifest.js} +27 -27
- package/src/install/{mcp.ts → mcp.js} +30 -30
- package/src/install/{paths.ts → paths.js} +15 -15
- package/src/install/{targets.ts → targets.js} +10 -10
- package/src/install/{tool-lifecycle.ts → tool-lifecycle.js} +20 -20
- package/src/lifecycle/{bash-availability.ts → bash-availability.js} +1 -1
- package/src/lifecycle/coverage.js +57 -0
- package/src/lifecycle/hook-step.js +61 -0
- package/src/lifecycle/{index.ts → index.js} +160 -146
- package/src/lifecycle/{mcp-index.ts → mcp-index.js} +1 -1
- package/src/lifecycle/{policies.ts → policies.js} +11 -11
- package/src/lifecycle/{view.ts → view.js} +28 -28
- package/src/registry/{index.ts → index.js} +195 -66
- package/src/release/build-package.js +91 -0
- package/src/release/changelog-section.js +70 -0
- package/src/release/{sync-plugin-versions.ts → sync-plugin-versions.js} +4 -4
- package/src/runtime/advice/{cli.ts → cli.js} +31 -31
- package/src/runtime/advice/{client.ts → client.js} +44 -44
- package/src/runtime/advice/{config.ts → config.js} +16 -16
- package/src/runtime/advice/{doctor.ts → doctor.js} +32 -32
- package/src/runtime/advice/{endpoint-guard.ts → endpoint-guard.js} +20 -20
- package/src/runtime/advice/{kinds.ts → kinds.js} +16 -16
- package/src/runtime/advice/providers/{ollama-systemone.ts → ollama-systemone.js} +21 -21
- package/src/runtime/advice/{record.ts → record.js} +19 -19
- package/src/runtime/advice/{registry.ts → registry.js} +16 -16
- package/src/runtime/advice/{strength.ts → strength.js} +7 -7
- package/src/runtime/{brief-tools.ts → brief-tools.js} +50 -50
- package/src/runtime/{c4-project.ts → c4-project.js} +27 -27
- package/src/runtime/candidate.js +505 -0
- package/src/runtime/{capability-router.ts → capability-router.js} +39 -39
- package/src/runtime/{checkouts.ts → checkouts.js} +21 -21
- package/src/runtime/{claims.ts → claims.js} +44 -35
- package/src/runtime/{cli-result.ts → cli-result.js} +15 -8
- package/src/runtime/{cli.ts → cli.js} +59 -292
- package/src/runtime/{command-detect.ts → command-detect.js} +47 -47
- package/src/runtime/context/budget.js +203 -0
- package/src/runtime/context/compose.js +1367 -0
- package/src/runtime/context/delivery-store.js +102 -0
- package/src/runtime/context/mandatory.js +594 -0
- package/src/runtime/context/pointer.js +142 -0
- package/src/runtime/context-pack.js +1277 -0
- package/src/runtime/{decision-register.ts → decision-register.js} +67 -67
- package/src/runtime/evidence-fields.js +39 -0
- package/src/runtime/{evidence-ledger.ts → evidence-ledger.js} +32 -25
- package/src/runtime/failure/{capture.ts → capture.js} +29 -29
- package/src/runtime/failure/{classifier.ts → classifier.js} +4 -4
- package/src/runtime/failure/{cli.ts → cli.js} +30 -30
- package/src/runtime/failure/{home.ts → home.js} +6 -6
- package/src/runtime/failure/{store.ts → store.js} +32 -32
- package/src/runtime/{feature-resolve.ts → feature-resolve.js} +6 -6
- package/src/runtime/{freshness.ts → freshness.js} +59 -22
- package/src/runtime/{health.ts → health.js} +72 -72
- package/src/runtime/{history-compactor.ts → history-compactor.js} +25 -25
- package/src/runtime/{implementation-gate.ts → implementation-gate.js} +30 -30
- package/src/runtime/inventory/{coverage.ts → coverage.js} +10 -10
- package/src/runtime/inventory/{identity.ts → identity.js} +2 -2
- package/src/runtime/inventory/{index.ts → index.js} +36 -36
- package/src/runtime/inventory/{precedence.ts → precedence.js} +2 -2
- package/src/runtime/inventory/{read-scopes.ts → read-scopes.js} +9 -9
- package/src/runtime/inventory/{siblings.ts → siblings.js} +15 -15
- package/src/runtime/knowledge/{chunker.ts → chunker.js} +12 -12
- package/src/runtime/knowledge/{hybrid.ts → hybrid.js} +17 -17
- package/src/runtime/knowledge/{index-store.ts → index-store.js} +23 -23
- package/src/runtime/knowledge/{retrieval.ts → retrieval.js} +27 -27
- package/src/runtime/{leak-scan.ts → leak-scan.js} +12 -12
- package/src/runtime/lifecycle/{cli.ts → cli.js} +32 -32
- package/src/runtime/lifecycle/{event-store.ts → event-store.js} +48 -48
- package/src/runtime/lifecycle/{fold.ts → fold.js} +41 -41
- package/src/runtime/lifecycle/{followup.ts → followup.js} +54 -54
- package/src/runtime/lifecycle/{goal.ts → goal.js} +50 -50
- package/src/runtime/lifecycle/{intent-writer.ts → intent-writer.js} +5 -5
- package/src/runtime/lifecycle/{overview.ts → overview.js} +47 -47
- package/src/runtime/lifecycle/{release.ts → release.js} +29 -29
- package/src/runtime/lifecycle/{report-store.ts → report-store.js} +25 -25
- package/src/runtime/lifecycle/{retention.ts → retention.js} +37 -37
- package/src/runtime/lifecycle/{root.ts → root.js} +9 -9
- package/src/runtime/lifecycle/{status.ts → status.js} +64 -64
- package/src/runtime/lifecycle/{store-upkeep.ts → store-upkeep.js} +46 -46
- package/src/runtime/{locator-resolve.ts → locator-resolve.js} +7 -7
- package/src/runtime/{mask.ts → mask.js} +22 -22
- package/src/runtime/{mcp-consultations.ts → mcp-consultations.js} +54 -54
- package/src/runtime/{mcp-reach.ts → mcp-reach.js} +24 -24
- package/src/runtime/{model-router.ts → model-router.js} +20 -20
- package/src/runtime/{outcome.ts → outcome.js} +27 -27
- package/src/runtime/{reach.ts → reach.js} +21 -21
- package/src/runtime/{readiness-record.ts → readiness-record.js} +12 -12
- package/src/runtime/{readiness.ts → readiness.js} +113 -43
- package/src/runtime/{recovery.ts → recovery.js} +15 -15
- package/src/runtime/{research-request.ts → research-request.js} +34 -34
- package/src/runtime/{retrieval-plan.ts → retrieval-plan.js} +453 -104
- package/src/runtime/retrieval-record.js +223 -0
- package/src/runtime/scaffold/{artifacts.ts → artifacts.js} +22 -19
- package/src/runtime/scaffold/{fingerprint.ts → fingerprint.js} +3 -3
- package/src/runtime/scaffold/{generate.ts → generate.js} +109 -109
- package/src/runtime/scaffold/{languages.ts → languages.js} +34 -34
- package/src/runtime/source-version.js +289 -0
- package/src/runtime/storage/{cli.ts → cli.js} +9 -9
- package/src/runtime/storage/{engine.ts → engine.js} +37 -37
- package/src/runtime/storage/{registry.ts → registry.js} +19 -19
- package/src/runtime/storage/{roots.ts → roots.js} +8 -8
- package/src/runtime/storage/{rules.ts → rules.js} +26 -26
- package/src/runtime/storage/{upkeep.ts → upkeep.js} +35 -35
- package/src/runtime/storage/{work-status.ts → work-status.js} +39 -39
- package/src/runtime/{task-attempts.ts → task-attempts.js} +62 -62
- package/src/runtime/{task-classifier.ts → task-classifier.js} +34 -34
- package/src/runtime/{task-scope.ts → task-scope.js} +40 -15
- package/src/runtime/{task-state.ts → task-state.js} +86 -21
- package/src/runtime/trace/{indicators.ts → indicators.js} +22 -22
- package/src/runtime/trace/{ledger.ts → ledger.js} +42 -42
- package/src/runtime/trace/{render.ts → render.js} +7 -7
- package/src/runtime/trace/{views.ts → views.js} +40 -40
- package/src/runtime/verification/{contract-runner.ts → contract-runner.js} +23 -23
- package/src/runtime/verification/{engine.ts → engine.js} +143 -143
- package/src/runtime/verification/{registry.ts → registry.js} +6 -6
- package/src/runtime/verification/{scope-bound.ts → scope-bound.js} +50 -50
- package/src/runtime/verification/{test-selection.ts → test-selection.js} +28 -28
- package/src/runtime/{workflow-engine.ts → workflow-engine.js} +50 -50
- package/src/runtime/{workflow-orchestrator.ts → workflow-orchestrator.js} +91 -91
- package/src/runtime/{worktree.ts → worktree.js} +31 -31
- package/src/state/{index.ts → index.js} +26 -26
- package/src/state/{lockfile.ts → lockfile.js} +20 -20
- package/src/state/{run-lock.ts → run-lock.js} +44 -44
- package/core/.antigravity-plugin/hooks.json +0 -39
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
- package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
- package/core/harnesses/shared/hooks/hooks.json +0 -47
- package/src/runtime/context-pack.ts +0 -492
|
@@ -12,7 +12,7 @@ const {
|
|
|
12
12
|
buildStats,
|
|
13
13
|
buildDiscover,
|
|
14
14
|
PROVIDER,
|
|
15
|
-
} = require('./views.
|
|
15
|
+
} = require('./views.js');
|
|
16
16
|
const {
|
|
17
17
|
shortTime,
|
|
18
18
|
formatMs,
|
|
@@ -20,7 +20,7 @@ const {
|
|
|
20
20
|
integrityNote,
|
|
21
21
|
printEmptyLedgerNotice,
|
|
22
22
|
DISCOVER_MARK,
|
|
23
|
-
} = require('./render.
|
|
23
|
+
} = require('./render.js');
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Task-run telemetry — the JavaScript port of
|
|
@@ -49,7 +49,7 @@ const {
|
|
|
49
49
|
* They price one specific model; a trace naming a different one gets a cost only if the caller
|
|
50
50
|
* supplies its rates, because inventing a price for an unpriced model is inventing a metric.
|
|
51
51
|
*/
|
|
52
|
-
const DEFAULT_TOKEN_RATES
|
|
52
|
+
const DEFAULT_TOKEN_RATES = Object.freeze({
|
|
53
53
|
inputPerToken: 0.000003,
|
|
54
54
|
outputPerToken: 0.000015,
|
|
55
55
|
});
|
|
@@ -60,20 +60,20 @@ const COST_DECIMALS = 6;
|
|
|
60
60
|
* @param {number} value
|
|
61
61
|
* @returns {number} rounded to six decimals, matching the Python's `round(x, 6)`
|
|
62
62
|
*/
|
|
63
|
-
function roundCost(value
|
|
63
|
+
function roundCost(value ) {
|
|
64
64
|
const factor = 10 ** COST_DECIMALS;
|
|
65
65
|
return Math.round(value * factor) / factor;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
class TaskRunTelemetry {
|
|
69
|
-
taskId
|
|
70
|
-
agent
|
|
71
|
-
model
|
|
72
|
-
rates
|
|
73
|
-
events
|
|
74
|
-
clock
|
|
75
|
-
startedAtMs
|
|
76
|
-
startedAt
|
|
69
|
+
taskId ;
|
|
70
|
+
agent ;
|
|
71
|
+
model ;
|
|
72
|
+
rates ;
|
|
73
|
+
events ;
|
|
74
|
+
clock ;
|
|
75
|
+
startedAtMs ;
|
|
76
|
+
startedAt ;
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
79
|
* @param {string} taskId
|
|
@@ -84,7 +84,7 @@ class TaskRunTelemetry {
|
|
|
84
84
|
* @param {{inputPerToken: number, outputPerToken: number}|null} [options.rates]
|
|
85
85
|
* @param {Function} [options.clock] injection seam for tests; returns elapsed milliseconds
|
|
86
86
|
*/
|
|
87
|
-
constructor(taskId
|
|
87
|
+
constructor(taskId , options = {}) {
|
|
88
88
|
const opts = typeof options === 'string' ? { agent: options } : options;
|
|
89
89
|
this.taskId = taskId;
|
|
90
90
|
this.agent = opts.agent || 'codex';
|
|
@@ -105,7 +105,7 @@ class TaskRunTelemetry {
|
|
|
105
105
|
* @param {Object} [details={}]
|
|
106
106
|
* @returns {Object} the recorded event
|
|
107
107
|
*/
|
|
108
|
-
recordEvent(eventType
|
|
108
|
+
recordEvent(eventType , details = {}) {
|
|
109
109
|
const event = {
|
|
110
110
|
timestamp: new Date().toISOString(),
|
|
111
111
|
type: eventType,
|
|
@@ -119,7 +119,7 @@ class TaskRunTelemetry {
|
|
|
119
119
|
* Milliseconds elapsed since the recorder was constructed.
|
|
120
120
|
* @returns {number}
|
|
121
121
|
*/
|
|
122
|
-
elapsedMs()
|
|
122
|
+
elapsedMs() {
|
|
123
123
|
return Math.max(0, this.clock() - this.startedAtMs);
|
|
124
124
|
}
|
|
125
125
|
|
|
@@ -130,7 +130,7 @@ class TaskRunTelemetry {
|
|
|
130
130
|
* @param {number} [outputTokens=0]
|
|
131
131
|
* @returns {Object} trace record
|
|
132
132
|
*/
|
|
133
|
-
exportTrace(success
|
|
133
|
+
exportTrace(success , inputTokens = 0, outputTokens = 0) {
|
|
134
134
|
// Defect fix (2): cost is only reported when rates are known for this run. The default rates
|
|
135
135
|
// reproduce the Python's number exactly; a caller that clears them (`rates: null`) gets `null`
|
|
136
136
|
// rather than a confident figure derived from another model's price list.
|
|
@@ -180,7 +180,7 @@ const LEDGER_FILE_RE = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
|
180
180
|
* `wire` is the on-disk key: snake_case, matching the five fields `doflow-run` already writes, so
|
|
181
181
|
* one file never mixes two naming conventions.
|
|
182
182
|
*/
|
|
183
|
-
const RUN_FIELDS
|
|
183
|
+
const RUN_FIELDS = Object.freeze([
|
|
184
184
|
{ key: 'verb', wire: 'verb', type: 'token' },
|
|
185
185
|
{ key: 'kind', wire: 'kind', type: 'token' },
|
|
186
186
|
{ key: 'capability', wire: 'capability', type: 'token' },
|
|
@@ -215,7 +215,7 @@ const ISO_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?$/;
|
|
|
215
215
|
* @param {string} configDir an install root, e.g. `<project>/.doflow` or `~/.doflow`
|
|
216
216
|
* @returns {string}
|
|
217
217
|
*/
|
|
218
|
-
function runsDir(configDir
|
|
218
|
+
function runsDir(configDir ) {
|
|
219
219
|
return path.join(configDir, 'state', RUNS_DIRNAME);
|
|
220
220
|
}
|
|
221
221
|
|
|
@@ -234,8 +234,8 @@ function runsDir(configDir: string): string {
|
|
|
234
234
|
* subdirectory, so a project with state but no guidance tree falls through to the home install
|
|
235
235
|
* @returns {{dir: string, configDir: string, origin: string, searched: string[]}}
|
|
236
236
|
*/
|
|
237
|
-
function resolveRunsLocation({ start, global: forceGlobal = false, env = process.env, homeDir = os.homedir(), fsImpl = fs, containing = '' }
|
|
238
|
-
const searched
|
|
237
|
+
function resolveRunsLocation({ start, global: forceGlobal = false, env = process.env, homeDir = os.homedir(), fsImpl = fs, containing = '' } = {}) {
|
|
238
|
+
const searched = [];
|
|
239
239
|
const home = path.resolve(homeDir || '.');
|
|
240
240
|
if (forceGlobal) {
|
|
241
241
|
const configDir = path.join(home, '.doflow');
|
|
@@ -274,14 +274,14 @@ function resolveRunsLocation({ start, global: forceGlobal = false, env = process
|
|
|
274
274
|
* fields and never carries their values — a rejection report that echoed the value would leak
|
|
275
275
|
* exactly the content the rejection exists to keep out.
|
|
276
276
|
*/
|
|
277
|
-
function sanitizeRunEvent(input
|
|
277
|
+
function sanitizeRunEvent(input , { now = new Date() } = {}) {
|
|
278
278
|
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
279
279
|
return { record: null, dropped: [], reason: 'event must be an object' };
|
|
280
280
|
}
|
|
281
281
|
const byKey = new Map(RUN_FIELDS.map((field) => [field.key, field]));
|
|
282
282
|
const byWire = new Map(RUN_FIELDS.map((field) => [field.wire, field]));
|
|
283
|
-
const record
|
|
284
|
-
const dropped
|
|
283
|
+
const record = {};
|
|
284
|
+
const dropped = [];
|
|
285
285
|
|
|
286
286
|
const timestamp = typeof input.timestamp === 'string' && ISO_TIMESTAMP.test(input.timestamp)
|
|
287
287
|
? input.timestamp
|
|
@@ -317,9 +317,9 @@ function sanitizeRunEvent(input: any, { now = new Date() } = {}): any {
|
|
|
317
317
|
* @param {*} raw parsed JSON value
|
|
318
318
|
* @returns {Object|null} null when the line is not a usable record
|
|
319
319
|
*/
|
|
320
|
-
function normalizeRunRecord(raw
|
|
320
|
+
function normalizeRunRecord(raw ) {
|
|
321
321
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
|
|
322
|
-
const out
|
|
322
|
+
const out = { timestamp: typeof raw.timestamp === 'string' ? raw.timestamp : null };
|
|
323
323
|
for (const field of RUN_FIELDS) {
|
|
324
324
|
const value = raw[field.wire] !== undefined ? raw[field.wire] : raw[field.key];
|
|
325
325
|
if (value === undefined || value === null) continue;
|
|
@@ -337,9 +337,9 @@ function normalizeRunRecord(raw: any): any | null {
|
|
|
337
337
|
}
|
|
338
338
|
|
|
339
339
|
class RunLedger {
|
|
340
|
-
fsImpl
|
|
341
|
-
clock
|
|
342
|
-
dir
|
|
340
|
+
fsImpl ;
|
|
341
|
+
clock ;
|
|
342
|
+
dir ;
|
|
343
343
|
|
|
344
344
|
/**
|
|
345
345
|
* @param {Object} [options]
|
|
@@ -348,7 +348,7 @@ class RunLedger {
|
|
|
348
348
|
* @param {Object} [options.fsImpl=fs]
|
|
349
349
|
* @param {Function} [options.clock] returns the current Date; injection seam for tests
|
|
350
350
|
*/
|
|
351
|
-
constructor({ dir, configDir, fsImpl = fs, clock = () => new Date() }
|
|
351
|
+
constructor({ dir, configDir, fsImpl = fs, clock = () => new Date() } = {}) {
|
|
352
352
|
this.fsImpl = fsImpl;
|
|
353
353
|
this.clock = clock;
|
|
354
354
|
if (dir) this.dir = path.resolve(dir);
|
|
@@ -362,7 +362,7 @@ class RunLedger {
|
|
|
362
362
|
* @param {Object} event
|
|
363
363
|
* @returns {{written: boolean, file?: string, record?: Object, dropped: string[], reason?: string}}
|
|
364
364
|
*/
|
|
365
|
-
append(event
|
|
365
|
+
append(event ) {
|
|
366
366
|
const { record, dropped, reason } = sanitizeRunEvent(event, { now: this.clock() });
|
|
367
367
|
if (!record) return { written: false, dropped, reason };
|
|
368
368
|
try {
|
|
@@ -370,7 +370,7 @@ class RunLedger {
|
|
|
370
370
|
const file = path.join(this.dir, `${record.timestamp.slice(0, 10)}.jsonl`);
|
|
371
371
|
this.fsImpl.appendFileSync(file, `${JSON.stringify(record)}\n`, 'utf8');
|
|
372
372
|
return { written: true, file, record, dropped };
|
|
373
|
-
} catch (error
|
|
373
|
+
} catch (error ) {
|
|
374
374
|
return { written: false, record, dropped, reason: error.message };
|
|
375
375
|
}
|
|
376
376
|
}
|
|
@@ -383,8 +383,8 @@ class RunLedger {
|
|
|
383
383
|
* @param {number} [options.days] keep only partitions dated within the last N calendar days
|
|
384
384
|
* @returns {{dir: string, exists: boolean, files: string[], records: Object[], malformedLines: number, unreadableFiles: string[], days: number|null}}
|
|
385
385
|
*/
|
|
386
|
-
read({ days = null }
|
|
387
|
-
const result
|
|
386
|
+
read({ days = null } = {}) {
|
|
387
|
+
const result = {
|
|
388
388
|
dir: this.dir,
|
|
389
389
|
exists: false,
|
|
390
390
|
files: [],
|
|
@@ -394,7 +394,7 @@ class RunLedger {
|
|
|
394
394
|
days: days && days > 0 ? days : null,
|
|
395
395
|
};
|
|
396
396
|
|
|
397
|
-
let entries
|
|
397
|
+
let entries ;
|
|
398
398
|
try {
|
|
399
399
|
entries = this.fsImpl.readdirSync(this.dir);
|
|
400
400
|
} catch {
|
|
@@ -402,7 +402,7 @@ class RunLedger {
|
|
|
402
402
|
}
|
|
403
403
|
result.exists = true;
|
|
404
404
|
|
|
405
|
-
let names
|
|
405
|
+
let names = entries.filter((name) => LEDGER_FILE_RE.test(name)).sort();
|
|
406
406
|
if (result.days) {
|
|
407
407
|
// Calendar days, not "the last N files": a quiet week must not silently widen the window.
|
|
408
408
|
const cutoff = new Date(this.clock().getTime() - (result.days - 1) * 24 * 60 * 60 * 1000)
|
|
@@ -412,7 +412,7 @@ class RunLedger {
|
|
|
412
412
|
result.files = names;
|
|
413
413
|
|
|
414
414
|
for (const name of names) {
|
|
415
|
-
let text
|
|
415
|
+
let text ;
|
|
416
416
|
try {
|
|
417
417
|
text = this.fsImpl.readFileSync(path.join(this.dir, name), 'utf8');
|
|
418
418
|
} catch {
|
|
@@ -421,7 +421,7 @@ class RunLedger {
|
|
|
421
421
|
}
|
|
422
422
|
for (const line of text.split('\n')) {
|
|
423
423
|
if (line.trim() === '') continue;
|
|
424
|
-
let parsed
|
|
424
|
+
let parsed ;
|
|
425
425
|
try {
|
|
426
426
|
parsed = JSON.parse(line);
|
|
427
427
|
} catch {
|
|
@@ -436,7 +436,7 @@ class RunLedger {
|
|
|
436
436
|
}
|
|
437
437
|
|
|
438
438
|
// Undated records sort last rather than being dropped: they still count toward usage.
|
|
439
|
-
result.records.sort((a
|
|
439
|
+
result.records.sort((a , b ) => (a.at ?? Number.MAX_SAFE_INTEGER) - (b.at ?? Number.MAX_SAFE_INTEGER));
|
|
440
440
|
return result;
|
|
441
441
|
}
|
|
442
442
|
}
|
|
@@ -455,7 +455,7 @@ class RunLedger {
|
|
|
455
455
|
* @param {string} [options.projectRoot]
|
|
456
456
|
* @returns {number} process exit code
|
|
457
457
|
*/
|
|
458
|
-
function handleTraceCommand({ json = false, days = null, global: useGlobal = false, projectRoot }
|
|
458
|
+
function handleTraceCommand({ json = false, days = null, global: useGlobal = false, projectRoot } = {}) {
|
|
459
459
|
const location = resolveRunsLocation({ start: projectRoot, global: useGlobal });
|
|
460
460
|
const view = buildTrace(new RunLedger({ dir: location.dir }).read({ days }));
|
|
461
461
|
view.origin = location.origin;
|
|
@@ -503,7 +503,7 @@ function handleTraceCommand({ json = false, days = null, global: useGlobal = fal
|
|
|
503
503
|
* @param {Object} options
|
|
504
504
|
* @returns {number} process exit code
|
|
505
505
|
*/
|
|
506
|
-
function handleStatsCommand({ json = false, days = null, global: useGlobal = false, projectRoot }
|
|
506
|
+
function handleStatsCommand({ json = false, days = null, global: useGlobal = false, projectRoot } = {}) {
|
|
507
507
|
const location = resolveRunsLocation({ start: projectRoot, global: useGlobal });
|
|
508
508
|
const view = buildStats(new RunLedger({ dir: location.dir }).read({ days }));
|
|
509
509
|
view.origin = location.origin;
|
|
@@ -556,7 +556,7 @@ function handleStatsCommand({ json = false, days = null, global: useGlobal = fal
|
|
|
556
556
|
* @param {Object} [options.providerHealth] injected for tests; probed from `health.ts` otherwise
|
|
557
557
|
* @returns {number} process exit code — 1 when there is a finding to act on
|
|
558
558
|
*/
|
|
559
|
-
function handleDiscoverCommand({ json = false, days = null, global: useGlobal = false, projectRoot, providerHealth }
|
|
559
|
+
function handleDiscoverCommand({ json = false, days = null, global: useGlobal = false, projectRoot, providerHealth } = {}) {
|
|
560
560
|
const location = resolveRunsLocation({ start: projectRoot, global: useGlobal });
|
|
561
561
|
const read = new RunLedger({ dir: location.dir }).read({ days });
|
|
562
562
|
|
|
@@ -565,7 +565,7 @@ function handleDiscoverCommand({ json = false, days = null, global: useGlobal =
|
|
|
565
565
|
let health = providerHealth;
|
|
566
566
|
if (health === undefined) {
|
|
567
567
|
try {
|
|
568
|
-
health = require('../health.
|
|
568
|
+
health = require('../health.js').probeProviders({ ids: Object.values(PROVIDER) });
|
|
569
569
|
} catch {
|
|
570
570
|
health = null;
|
|
571
571
|
}
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
|
|
13
13
|
/** ISO timestamp to a compact local-independent `MM-DD HH:MM:SS`. Keeps columns narrow without
|
|
14
14
|
* inventing a timezone the record does not carry. */
|
|
15
|
-
function shortTime(timestamp
|
|
15
|
+
function shortTime(timestamp ) {
|
|
16
16
|
if (typeof timestamp !== 'string' || timestamp.length < 19) return '(undated)';
|
|
17
17
|
return `${timestamp.slice(5, 10)} ${timestamp.slice(11, 19)}`;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
function formatMs(value
|
|
20
|
+
function formatMs(value ) {
|
|
21
21
|
if (typeof value !== 'number') return '-';
|
|
22
22
|
if (value < 1000) return `${value}ms`;
|
|
23
23
|
return `${(value / 1000).toFixed(1)}s`;
|
|
@@ -33,7 +33,7 @@ function formatMs(value: any): string {
|
|
|
33
33
|
* @param {number} code
|
|
34
34
|
* @returns {number}
|
|
35
35
|
*/
|
|
36
|
-
function finish(code
|
|
36
|
+
function finish(code ) {
|
|
37
37
|
process.exitCode = code;
|
|
38
38
|
return code;
|
|
39
39
|
}
|
|
@@ -44,22 +44,22 @@ function finish(code: number): number {
|
|
|
44
44
|
* @param {Object} ledger
|
|
45
45
|
* @returns {string|null}
|
|
46
46
|
*/
|
|
47
|
-
function integrityNote(ledger
|
|
48
|
-
const parts
|
|
47
|
+
function integrityNote(ledger ) {
|
|
48
|
+
const parts = [];
|
|
49
49
|
if (ledger.malformedLines) parts.push(`${ledger.malformedLines} unreadable ledger line(s) skipped`);
|
|
50
50
|
if (ledger.unreadableFiles.length) parts.push(`${ledger.unreadableFiles.length} unreadable partition(s): ${ledger.unreadableFiles.join(', ')}`);
|
|
51
51
|
return parts.length ? `Note: ${parts.join('; ')}.` : null;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/** One place that turns a resolved location into the sentence an empty ledger deserves. */
|
|
55
|
-
function printEmptyLedgerNotice(ledger
|
|
55
|
+
function printEmptyLedgerNotice(ledger , reason ) {
|
|
56
56
|
console.log(` ${reason}`);
|
|
57
57
|
console.log(` Location: ${ledger.dir}`);
|
|
58
58
|
console.log(' An empty ledger is a normal state for a fresh install. It is not evidence that');
|
|
59
59
|
console.log(' nothing ran, and no conclusion about usage can be drawn from it.');
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
const DISCOVER_MARK
|
|
62
|
+
const DISCOVER_MARK = Object.freeze({
|
|
63
63
|
FINDING: '! FINDING',
|
|
64
64
|
CLEAR: '✓ CLEAR',
|
|
65
65
|
NOT_DETERMINED: '? UNKNOWN',
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
const SESSION_GAP_MS = 30 * 60 * 1000;
|
|
21
21
|
|
|
22
22
|
/** Nearest-rank percentile over an unsorted numeric array. */
|
|
23
|
-
function percentile(values
|
|
23
|
+
function percentile(values , fraction ) {
|
|
24
24
|
if (!values.length) return null;
|
|
25
25
|
const sorted = [...values].sort((a, b) => a - b);
|
|
26
26
|
const rank = Math.max(1, Math.ceil(fraction * sorted.length));
|
|
@@ -34,31 +34,31 @@ function percentile(values: number[], fraction: number): number | null {
|
|
|
34
34
|
* @param {number} [options.gapMs=SESSION_GAP_MS]
|
|
35
35
|
* @returns {{grouping: 'session-field'|'idle-gap', sessions: Array<{id: string, startedAt: string|null, endedAt: string|null, records: Object[]}>}}
|
|
36
36
|
*/
|
|
37
|
-
function groupSessions(records
|
|
37
|
+
function groupSessions(records , { gapMs = SESSION_GAP_MS } = {}) {
|
|
38
38
|
if (!records.length) return { grouping: 'idle-gap', sessions: [] };
|
|
39
39
|
|
|
40
40
|
// An explicit session id is a recorded fact; an idle gap is an inference. Prefer the fact, and
|
|
41
41
|
// only when every record carries one — a half-labelled history would mix the two silently.
|
|
42
42
|
if (records.every((record) => record.session)) {
|
|
43
|
-
const order
|
|
44
|
-
const byId = new Map
|
|
43
|
+
const order = [];
|
|
44
|
+
const byId = new Map ();
|
|
45
45
|
for (const record of records) {
|
|
46
46
|
if (!byId.has(record.session)) { byId.set(record.session, []); order.push(record.session); }
|
|
47
|
-
byId.get(record.session)
|
|
47
|
+
byId.get(record.session) .push(record);
|
|
48
48
|
}
|
|
49
49
|
return {
|
|
50
50
|
grouping: 'session-field',
|
|
51
51
|
sessions: order.map((id) => ({
|
|
52
52
|
id,
|
|
53
|
-
startedAt: byId.get(id)
|
|
54
|
-
endedAt: byId.get(id)
|
|
55
|
-
records: byId.get(id)
|
|
53
|
+
startedAt: byId.get(id) [0].timestamp,
|
|
54
|
+
endedAt: byId.get(id) [byId.get(id) .length - 1].timestamp,
|
|
55
|
+
records: byId.get(id) ,
|
|
56
56
|
})),
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
const sessions
|
|
61
|
-
let current
|
|
60
|
+
const sessions = [];
|
|
61
|
+
let current = null;
|
|
62
62
|
for (const record of records) {
|
|
63
63
|
const previous = current && current.records[current.records.length - 1];
|
|
64
64
|
const contiguous = previous && record.at !== null && previous.at !== null && record.at - previous.at <= gapMs;
|
|
@@ -73,7 +73,7 @@ function groupSessions(records: any[], { gapMs = SESSION_GAP_MS } = {}): any {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/** The ledger provenance every view carries, so a reader can always tell what was actually read. */
|
|
76
|
-
function ledgerSummary(read
|
|
76
|
+
function ledgerSummary(read ) {
|
|
77
77
|
return {
|
|
78
78
|
dir: read.dir,
|
|
79
79
|
exists: read.exists,
|
|
@@ -87,8 +87,8 @@ function ledgerSummary(read: any): any {
|
|
|
87
87
|
|
|
88
88
|
/** How many records carry each optional field. Every view reports this, because the difference
|
|
89
89
|
* between "no missed opportunities" and "no data to look for them in" is exactly this table. */
|
|
90
|
-
function fieldCoverage(records
|
|
91
|
-
const count = (predicate
|
|
90
|
+
function fieldCoverage(records ) {
|
|
91
|
+
const count = (predicate ) => records.filter(predicate).length;
|
|
92
92
|
return {
|
|
93
93
|
capability: count((r) => Boolean(r.capability)),
|
|
94
94
|
provider: count((r) => Boolean(r.provider)),
|
|
@@ -108,7 +108,7 @@ function fieldCoverage(records: any[]): any {
|
|
|
108
108
|
* @param {number} [options.gapMs]
|
|
109
109
|
* @returns {Object} view model
|
|
110
110
|
*/
|
|
111
|
-
function buildTrace(read
|
|
111
|
+
function buildTrace(read , { gapMs = SESSION_GAP_MS } = {}) {
|
|
112
112
|
const ledger = ledgerSummary(read);
|
|
113
113
|
const { grouping, sessions } = groupSessions(read.records, { gapMs });
|
|
114
114
|
if (!sessions.length) {
|
|
@@ -126,7 +126,7 @@ function buildTrace(read: any, { gapMs = SESSION_GAP_MS } = {}): any {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
const session = sessions[sessions.length - 1];
|
|
129
|
-
const steps = session.records.map((record
|
|
129
|
+
const steps = session.records.map((record , index ) => ({
|
|
130
130
|
n: index + 1,
|
|
131
131
|
timestamp: record.timestamp,
|
|
132
132
|
verb: record.verb,
|
|
@@ -151,9 +151,9 @@ function buildTrace(read: any, { gapMs = SESSION_GAP_MS } = {}): any {
|
|
|
151
151
|
startedAt: session.startedAt,
|
|
152
152
|
endedAt: session.endedAt,
|
|
153
153
|
wallDurationMs: start !== null && end !== null ? Math.max(0, end - start) : null,
|
|
154
|
-
dispatchedMs: steps.reduce((total
|
|
154
|
+
dispatchedMs: steps.reduce((total , step ) => total + (step.durationMs ?? 0), 0),
|
|
155
155
|
stepCount: steps.length,
|
|
156
|
-
failedSteps: steps.filter((step
|
|
156
|
+
failedSteps: steps.filter((step ) => step.exitCode !== null && step.exitCode !== 0).length,
|
|
157
157
|
steps,
|
|
158
158
|
},
|
|
159
159
|
};
|
|
@@ -166,13 +166,13 @@ function buildTrace(read: any, { gapMs = SESSION_GAP_MS } = {}): any {
|
|
|
166
166
|
* @param {Object} read result of `RunLedger#read`
|
|
167
167
|
* @returns {Object} view model
|
|
168
168
|
*/
|
|
169
|
-
function buildStats(read
|
|
170
|
-
const records
|
|
169
|
+
function buildStats(read ) {
|
|
170
|
+
const records = read.records;
|
|
171
171
|
const ledger = ledgerSummary(read);
|
|
172
172
|
const durations = records.map((r) => r.durationMs).filter((value) => typeof value === 'number');
|
|
173
173
|
|
|
174
|
-
const tally = (keyOf
|
|
175
|
-
const map = new Map
|
|
174
|
+
const tally = (keyOf ) => {
|
|
175
|
+
const map = new Map ();
|
|
176
176
|
for (const record of records) {
|
|
177
177
|
const key = keyOf(record);
|
|
178
178
|
if (!key) continue;
|
|
@@ -221,7 +221,7 @@ function buildStats(read: any): any {
|
|
|
221
221
|
byVerb: tally((r) => r.verb),
|
|
222
222
|
byCapability: tally((r) => r.capability),
|
|
223
223
|
byProvider: tally((r) => r.provider),
|
|
224
|
-
byDay: tally((r) => (r.timestamp ? r.timestamp.slice(0, 10) : null)).sort((a
|
|
224
|
+
byDay: tally((r) => (r.timestamp ? r.timestamp.slice(0, 10) : null)).sort((a , b ) => a.key.localeCompare(b.key)),
|
|
225
225
|
coverage: fieldCoverage(records),
|
|
226
226
|
};
|
|
227
227
|
}
|
|
@@ -230,7 +230,7 @@ function buildStats(read: any): any {
|
|
|
230
230
|
|
|
231
231
|
/** Capability ids the opportunity analyses reason about, kept in one place so a registry rename
|
|
232
232
|
* shows up as one edit rather than four scattered string literals. */
|
|
233
|
-
const CAPABILITY
|
|
233
|
+
const CAPABILITY = Object.freeze({
|
|
234
234
|
exactSearch: 'code.exact-search',
|
|
235
235
|
semanticSearch: 'code.semantic-search',
|
|
236
236
|
relationships: 'code.relationships',
|
|
@@ -238,7 +238,7 @@ const CAPABILITY: Record<string, string> = Object.freeze({
|
|
|
238
238
|
compress: 'command.compress',
|
|
239
239
|
});
|
|
240
240
|
|
|
241
|
-
const PROVIDER
|
|
241
|
+
const PROVIDER = Object.freeze({
|
|
242
242
|
semantic: 'semble.search',
|
|
243
243
|
structural: 'graphify.query',
|
|
244
244
|
compressor: 'rtk',
|
|
@@ -260,7 +260,7 @@ const FINDING = 'FINDING';
|
|
|
260
260
|
const CLEAR = 'CLEAR';
|
|
261
261
|
|
|
262
262
|
/** Health of a provider as this analysis needs it: only `HEALTHY` counts as "was available". */
|
|
263
|
-
function providerAvailable(providerHealth
|
|
263
|
+
function providerAvailable(providerHealth , id ) {
|
|
264
264
|
const entry = providerHealth ? providerHealth[id] : undefined;
|
|
265
265
|
if (!entry) return null; // unknown — not the same as unavailable
|
|
266
266
|
return entry.status === 'HEALTHY';
|
|
@@ -281,8 +281,8 @@ function providerAvailable(providerHealth: any, id: string): boolean | null {
|
|
|
281
281
|
* @param {number} [options.gapMs]
|
|
282
282
|
* @returns {Object} view model
|
|
283
283
|
*/
|
|
284
|
-
function buildDiscover(read
|
|
285
|
-
const records
|
|
284
|
+
function buildDiscover(read , { providerHealth = null, gapMs = SESSION_GAP_MS } = {}) {
|
|
285
|
+
const records = read.records;
|
|
286
286
|
const ledger = ledgerSummary(read);
|
|
287
287
|
const coverage = fieldCoverage(records);
|
|
288
288
|
const { grouping, sessions } = groupSessions(records, { gapMs });
|
|
@@ -296,7 +296,7 @@ function buildDiscover(read: any, { providerHealth = null, gapMs = SESSION_GAP_M
|
|
|
296
296
|
+ 'Capability-level records are written by runtime calls that resolve a capability to a provider.',
|
|
297
297
|
};
|
|
298
298
|
|
|
299
|
-
const analyses
|
|
299
|
+
const analyses = [];
|
|
300
300
|
|
|
301
301
|
// 1. A broad search that was really a semantic question.
|
|
302
302
|
analyses.push((() => {
|
|
@@ -332,13 +332,13 @@ function buildDiscover(read: any, { providerHealth = null, gapMs = SESSION_GAP_M
|
|
|
332
332
|
if (!available) return { ...base, status: NOT_APPLICABLE, detail: `\`${PROVIDER.structural}\` is not answering on this machine, so structural queries were not an available alternative.` };
|
|
333
333
|
|
|
334
334
|
const offenders = sessions
|
|
335
|
-
.map((session
|
|
335
|
+
.map((session ) => ({
|
|
336
336
|
id: session.id,
|
|
337
337
|
startedAt: session.startedAt,
|
|
338
|
-
searches: session.records.filter((r
|
|
339
|
-
structural: session.records.filter((r
|
|
338
|
+
searches: session.records.filter((r ) => r.capability === CAPABILITY.exactSearch).length,
|
|
339
|
+
structural: session.records.filter((r ) => r.capability === CAPABILITY.relationships || r.capability === CAPABILITY.impact).length,
|
|
340
340
|
}))
|
|
341
|
-
.filter((session
|
|
341
|
+
.filter((session ) => session.searches >= REPEATED_SEARCHES && session.structural === 0);
|
|
342
342
|
if (!offenders.length) return { ...base, status: CLEAR, detail: `no session ran ${REPEATED_SEARCHES}+ exact searches without also querying the code graph.` };
|
|
343
343
|
return {
|
|
344
344
|
...base,
|
|
@@ -362,7 +362,7 @@ function buildDiscover(read: any, { providerHealth = null, gapMs = SESSION_GAP_M
|
|
|
362
362
|
|
|
363
363
|
const bulky = records.filter((r) => (r.outputBytes ?? 0) >= LARGE_OUTPUT_BYTES && r.provider !== PROVIDER.compressor);
|
|
364
364
|
if (!bulky.length) return { ...base, status: CLEAR, detail: `no uncompressed run returned ${LARGE_OUTPUT_BYTES} bytes or more.` };
|
|
365
|
-
const wasted = bulky.reduce((total
|
|
365
|
+
const wasted = bulky.reduce((total , r ) => total + r.outputBytes, 0);
|
|
366
366
|
return {
|
|
367
367
|
...base,
|
|
368
368
|
status: FINDING,
|
|
@@ -379,14 +379,14 @@ function buildDiscover(read: any, { providerHealth = null, gapMs = SESSION_GAP_M
|
|
|
379
379
|
const base = { id: 'retries-without-readiness', title: 'Implementation retries with no readiness check beforehand' };
|
|
380
380
|
if (!records.length) return { ...base, status: NOT_DETERMINED, detail: 'the ledger holds no runs to analyse.' };
|
|
381
381
|
const retrying = sessions
|
|
382
|
-
.map((session
|
|
382
|
+
.map((session ) => ({
|
|
383
383
|
id: session.id,
|
|
384
384
|
startedAt: session.startedAt,
|
|
385
|
-
failedVerifications: session.records.filter((r
|
|
386
|
-
recoveries: session.records.filter((r
|
|
387
|
-
readinessCalls: session.records.filter((r
|
|
385
|
+
failedVerifications: session.records.filter((r ) => r.verb === 'verify' && r.exitCode !== undefined && r.exitCode !== 0).length,
|
|
386
|
+
recoveries: session.records.filter((r ) => r.verb === 'recover').length,
|
|
387
|
+
readinessCalls: session.records.filter((r ) => r.verb === 'readiness').length,
|
|
388
388
|
}))
|
|
389
|
-
.filter((session
|
|
389
|
+
.filter((session ) => session.failedVerifications >= RETRY_THRESHOLD || session.recoveries >= 1);
|
|
390
390
|
if (!retrying.length) {
|
|
391
391
|
// The retry signal is a `verify` that FAILED (non-zero exit_code) or a `recover` run. A
|
|
392
392
|
// `verify` record with no exit_code carries neither, and that is the shape the shell
|
|
@@ -408,7 +408,7 @@ function buildDiscover(read: any, { providerHealth = null, gapMs = SESSION_GAP_M
|
|
|
408
408
|
: 'no `verify` with a recorded exit code and no `recover` run appears in this window, so there is no retry behaviour to judge.',
|
|
409
409
|
};
|
|
410
410
|
}
|
|
411
|
-
const unguarded = retrying.filter((session
|
|
411
|
+
const unguarded = retrying.filter((session ) => session.readinessCalls === 0);
|
|
412
412
|
if (!unguarded.length) {
|
|
413
413
|
return {
|
|
414
414
|
...base,
|
|
@@ -430,7 +430,7 @@ function buildDiscover(read: any, { providerHealth = null, gapMs = SESSION_GAP_M
|
|
|
430
430
|
const undetermined = analyses.filter((analysis) => analysis.status === NOT_DETERMINED);
|
|
431
431
|
|
|
432
432
|
// The verdict never claims a clean bill of health over data that could not answer the question.
|
|
433
|
-
let verdict
|
|
433
|
+
let verdict ;
|
|
434
434
|
if (findings.length) verdict = 'findings';
|
|
435
435
|
else if (undetermined.length === analyses.length) verdict = 'insufficient-data';
|
|
436
436
|
else if (undetermined.length) verdict = 'no-findings-in-the-analyses-that-could-run';
|