@khoavu882/doflow 2.0.0-beta.3 → 2.0.0-beta.4
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/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/registry/storage.json +87 -83
- package/core/registry/verification.json +1 -1
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -2
- package/core/shared/skills/do-execute-plan/references/scaffold.md +2 -2
- package/package.json +4 -2
- package/src/adapters/antigravity/{index.ts → index.js} +60 -60
- package/src/adapters/claude/{index.ts → index.js} +48 -48
- 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} +23 -23
- package/src/adapters/codex/{index.ts → index.js} +98 -98
- 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} +70 -70
- package/src/adapters/gemini/{hooks.ts → hooks.js} +24 -24
- package/src/adapters/gemini/{index.ts → index.js} +41 -41
- 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} +34 -34
- 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/{index.ts → index.js} +30 -30
- 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} +24 -24
- 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} +19 -19
- package/src/cli/{index.ts → index.js} +26 -26
- package/src/cli/{runtime-commands.ts → runtime-commands.js} +48 -48
- 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/{index.ts → index.js} +140 -140
- 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} +60 -60
- 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/{capability-router.ts → capability-router.js} +39 -39
- package/src/runtime/{checkouts.ts → checkouts.js} +21 -21
- package/src/runtime/{claims.ts → claims.js} +30 -30
- package/src/runtime/{cli-result.ts → cli-result.js} +4 -4
- package/src/runtime/{cli.ts → cli.js} +37 -37
- package/src/runtime/{command-detect.ts → command-detect.js} +47 -47
- package/src/runtime/{context-pack.ts → context-pack.js} +20 -20
- package/src/runtime/{decision-register.ts → decision-register.js} +67 -67
- package/src/runtime/{evidence-ledger.ts → evidence-ledger.js} +25 -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} +15 -15
- 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} +35 -35
- 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} +40 -40
- package/src/runtime/scaffold/{artifacts.ts → artifacts.js} +19 -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/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} +61 -61
- package/src/runtime/{task-classifier.ts → task-classifier.js} +34 -34
- package/src/runtime/{task-scope.ts → task-scope.js} +13 -13
- package/src/runtime/{task-state.ts → task-state.js} +15 -15
- 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
|
@@ -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';
|
|
@@ -46,7 +46,7 @@ const TIMEOUT_EXIT_CODE = 124;
|
|
|
46
46
|
* @param {*} value
|
|
47
47
|
* @returns {string}
|
|
48
48
|
*/
|
|
49
|
-
function truncate(value
|
|
49
|
+
function truncate(value ) {
|
|
50
50
|
if (typeof value !== 'string') return '';
|
|
51
51
|
if (value.length <= MAX_STREAM_CHARS) return value;
|
|
52
52
|
// Keep the TAIL, not the head, and say so.
|
|
@@ -77,8 +77,8 @@ const TAP_NOT_OK = /^\s*not ok\b(?:\s+\d+)?(?:\s*-\s*)?(.*)$/gm;
|
|
|
77
77
|
* @param {string} text
|
|
78
78
|
* @returns {Array<string>} unique identifiers, bounded by MAX_FAILED_TESTS
|
|
79
79
|
*/
|
|
80
|
-
function extractFailedTests(text
|
|
81
|
-
const seen = new Set
|
|
80
|
+
function extractFailedTests(text ) {
|
|
81
|
+
const seen = new Set ();
|
|
82
82
|
for (const match of text.matchAll(TAP_NOT_OK)) {
|
|
83
83
|
const name = match[1].trim();
|
|
84
84
|
if (name === '' || /#\s*(?:SKIP|TODO)\b/i.test(name)) continue;
|
|
@@ -89,12 +89,12 @@ function extractFailedTests(text: string): string[] {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
class VerificationContractRunner {
|
|
92
|
-
cwd
|
|
93
|
-
defaultTimeoutMs
|
|
94
|
-
exec
|
|
95
|
-
fsImpl
|
|
96
|
-
logDir
|
|
97
|
-
logWriteWarned
|
|
92
|
+
cwd ;
|
|
93
|
+
defaultTimeoutMs ;
|
|
94
|
+
exec ;
|
|
95
|
+
fsImpl ;
|
|
96
|
+
logDir ;
|
|
97
|
+
logWriteWarned ;
|
|
98
98
|
|
|
99
99
|
/**
|
|
100
100
|
* @param {Object|string} [options] a string is accepted for parity with the Python's positional
|
|
@@ -103,7 +103,7 @@ class VerificationContractRunner {
|
|
|
103
103
|
* @param {number} [options.defaultTimeoutMs=60000]
|
|
104
104
|
* @param {Function} [options.exec] injection seam for tests; same contract as `spawnSync`
|
|
105
105
|
*/
|
|
106
|
-
constructor(options
|
|
106
|
+
constructor(options = {}) {
|
|
107
107
|
const opts = typeof options === 'string' ? { cwd: options } : options;
|
|
108
108
|
this.cwd = opts.cwd || process.cwd();
|
|
109
109
|
this.defaultTimeoutMs = opts.defaultTimeoutMs || DEFAULT_TIMEOUT_MS;
|
|
@@ -123,7 +123,7 @@ class VerificationContractRunner {
|
|
|
123
123
|
* @param {string} stderr
|
|
124
124
|
* @returns {string|null} the log path, or null when the log could not be written
|
|
125
125
|
*/
|
|
126
|
-
persistLog(name
|
|
126
|
+
persistLog(name , command , stdout , stderr ) {
|
|
127
127
|
try {
|
|
128
128
|
this.fsImpl.mkdirSync(this.logDir, { recursive: true });
|
|
129
129
|
const safe = name.replace(/[^A-Za-z0-9._-]+/g, '_');
|
|
@@ -132,7 +132,7 @@ class VerificationContractRunner {
|
|
|
132
132
|
`# check: ${name}\n# command: ${command}\n--- stdout ---\n${stdout || ''}\n--- stderr ---\n${stderr || ''}\n`,
|
|
133
133
|
'utf8');
|
|
134
134
|
return file;
|
|
135
|
-
} catch (error
|
|
135
|
+
} catch (error ) {
|
|
136
136
|
// An unwritable log directory must not turn a completed check into an error — the check's
|
|
137
137
|
// verdict stands; only the pointer is missing, and its null says so. But a chronic
|
|
138
138
|
// permissions problem should not be invisible either: say so once per runner, on stderr so
|
|
@@ -154,7 +154,7 @@ class VerificationContractRunner {
|
|
|
154
154
|
* @param {string} [stdout]
|
|
155
155
|
* @param {string} [stderr]
|
|
156
156
|
*/
|
|
157
|
-
decorate(result
|
|
157
|
+
decorate(result , command , stdout , stderr ) {
|
|
158
158
|
const full = `${stdout || ''}\n${stderr || ''}`;
|
|
159
159
|
if (result.status === 'FAIL') {
|
|
160
160
|
const failedTests = extractFailedTests(full);
|
|
@@ -162,7 +162,7 @@ class VerificationContractRunner {
|
|
|
162
162
|
}
|
|
163
163
|
const truncated = (stdout || '').length > MAX_STREAM_CHARS || (stderr || '').length > MAX_STREAM_CHARS;
|
|
164
164
|
if ((result.status === 'FAIL' || truncated) && full.trim() !== '') {
|
|
165
|
-
result.logPath = this.persistLog(result.name, command, stdout
|
|
165
|
+
result.logPath = this.persistLog(result.name, command, stdout , stderr );
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
|
|
@@ -177,7 +177,7 @@ class VerificationContractRunner {
|
|
|
177
177
|
* evaluations — a recovery retry must observe live behaviour, so each pass passes a fresh map.
|
|
178
178
|
* @returns {Object} check result
|
|
179
179
|
*/
|
|
180
|
-
runCheck(name
|
|
180
|
+
runCheck(name , command , timeoutMs = this.defaultTimeoutMs, dedupeCache = null) {
|
|
181
181
|
const checkName = name || 'unnamed_check';
|
|
182
182
|
|
|
183
183
|
// Defect fix (1): the Python defaulted a missing command to `true`, so a typo in a contract
|
|
@@ -196,12 +196,12 @@ class VerificationContractRunner {
|
|
|
196
196
|
if (dedupeCache && dedupeCache.has(dedupeKey)) {
|
|
197
197
|
return { ...dedupeCache.get(dedupeKey), name: checkName, deduplicated: true };
|
|
198
198
|
}
|
|
199
|
-
const remember = (result
|
|
199
|
+
const remember = (result ) => {
|
|
200
200
|
if (dedupeCache) dedupeCache.set(dedupeKey, result);
|
|
201
201
|
return result;
|
|
202
202
|
};
|
|
203
203
|
|
|
204
|
-
let res
|
|
204
|
+
let res ;
|
|
205
205
|
try {
|
|
206
206
|
res = this.exec(command, {
|
|
207
207
|
shell: true,
|
|
@@ -214,7 +214,7 @@ class VerificationContractRunner {
|
|
|
214
214
|
// past anything a check legitimately prints. Only the first 2,000 chars are ever kept.
|
|
215
215
|
maxBuffer: 64 * 1024 * 1024,
|
|
216
216
|
});
|
|
217
|
-
} catch (error
|
|
217
|
+
} catch (error ) {
|
|
218
218
|
// Mirrors the Python's bare `except Exception` arm: an unspawnable command is a FAIL, not a
|
|
219
219
|
// thrown error that aborts the whole contract.
|
|
220
220
|
return remember({
|
|
@@ -263,7 +263,7 @@ class VerificationContractRunner {
|
|
|
263
263
|
const exitCode = res.status === null || res.status === undefined ? 1 : res.status;
|
|
264
264
|
const passed = res.status === 0;
|
|
265
265
|
|
|
266
|
-
const result
|
|
266
|
+
const result = {
|
|
267
267
|
name: checkName,
|
|
268
268
|
command,
|
|
269
269
|
status: passed ? 'PASS' : 'FAIL',
|
|
@@ -283,7 +283,7 @@ class VerificationContractRunner {
|
|
|
283
283
|
* @param {Array<{name?: string, command?: string, timeoutMs?: number}>} checks
|
|
284
284
|
* @returns {{ status: string, checks: Array<Object>, failedChecks: Array<string>, timestamp: string, reason?: string }}
|
|
285
285
|
*/
|
|
286
|
-
evaluateContract(checks
|
|
286
|
+
evaluateContract(checks ) {
|
|
287
287
|
if (!Array.isArray(checks)) {
|
|
288
288
|
throw new Error('evaluateContract expects an array of checks');
|
|
289
289
|
}
|
|
@@ -303,10 +303,10 @@ class VerificationContractRunner {
|
|
|
303
303
|
};
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
const results
|
|
307
|
-
const failedChecks
|
|
306
|
+
const results = [];
|
|
307
|
+
const failedChecks = [];
|
|
308
308
|
let overallStatus = 'PASS';
|
|
309
|
-
const dedupeCache = new Map
|
|
309
|
+
const dedupeCache = new Map (); // one evaluation, one cache — a retry pass starts fresh
|
|
310
310
|
|
|
311
311
|
for (const chk of checks) {
|
|
312
312
|
const name = (chk && chk.name) || 'unnamed_check';
|