@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
|
@@ -5,25 +5,25 @@
|
|
|
5
5
|
const fs = require('node:fs');
|
|
6
6
|
const path = require('node:path');
|
|
7
7
|
const crypto = require('node:crypto');
|
|
8
|
-
const { resolveStorePath } = require('../storage/roots.
|
|
8
|
+
const { resolveStorePath } = require('../storage/roots.js');
|
|
9
9
|
|
|
10
10
|
const RETAINED_DAYS = 90;
|
|
11
11
|
const PARTITION_NAME = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
12
12
|
|
|
13
|
-
function timestamp(now
|
|
13
|
+
function timestamp(now = () => new Date()) {
|
|
14
14
|
const value = typeof now === 'function' ? now() : now;
|
|
15
15
|
const date = new Date(value);
|
|
16
16
|
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
function selectedStore(roots
|
|
19
|
+
function selectedStore(roots , requestedStore ) {
|
|
20
20
|
if (requestedStore && roots?.[requestedStore]) return { store: requestedStore, root: roots[requestedStore] };
|
|
21
21
|
if (roots?.['project-state']) return { store: 'project-state', root: roots['project-state'] };
|
|
22
22
|
if (roots?.['user-state']) return { store: 'user-state', root: roots['user-state'] };
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function appendLine({ roots, at, line, fsImpl = fs, store }
|
|
26
|
+
function appendLine({ roots, at, line, fsImpl = fs, store } ) {
|
|
27
27
|
try {
|
|
28
28
|
const selected = selectedStore(roots, store);
|
|
29
29
|
if (!selected) return null;
|
|
@@ -31,7 +31,7 @@ function appendLine({ roots, at, line, fsImpl = fs, store }: any): { file: strin
|
|
|
31
31
|
if (!directory) return null;
|
|
32
32
|
const file = path.join(directory, `${at.slice(0, 10)}.jsonl`);
|
|
33
33
|
try { if (fsImpl.lstatSync(file).isSymbolicLink()) return null; }
|
|
34
|
-
catch (error
|
|
34
|
+
catch (error ) { if (error.code !== 'ENOENT') return null; }
|
|
35
35
|
fsImpl.mkdirSync(path.dirname(file), { recursive: true });
|
|
36
36
|
fsImpl.appendFileSync(file, `${JSON.stringify(line)}\n`, 'utf8');
|
|
37
37
|
return { file, store: selected.store };
|
|
@@ -43,7 +43,7 @@ function appendLine({ roots, at, line, fsImpl = fs, store }: any): { file: strin
|
|
|
43
43
|
function appendPick({
|
|
44
44
|
roots, now = () => new Date(), fsImpl = fs, kind, answer, question,
|
|
45
45
|
staticAnswer = null, provider = null, model = null, state, caller, taskId = null, durationMs = 0,
|
|
46
|
-
}
|
|
46
|
+
} = {}) {
|
|
47
47
|
try {
|
|
48
48
|
if (typeof state !== 'string' || !answer || !question) return null;
|
|
49
49
|
const at = timestamp(now);
|
|
@@ -54,7 +54,7 @@ function appendPick({
|
|
|
54
54
|
: question.type === 'score' ? [...(question.criteria || [])] : ['no', 'yes'];
|
|
55
55
|
const instructions = kind === 'adhoc' && typeof question.instructions === 'string'
|
|
56
56
|
? question.instructions.slice(0, 500) : null;
|
|
57
|
-
const line
|
|
57
|
+
const line = {
|
|
58
58
|
schema: 1,
|
|
59
59
|
type: 'pick',
|
|
60
60
|
pickId,
|
|
@@ -90,7 +90,7 @@ function appendPick({
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
function appendMiss({ roots, now = () => new Date(), fsImpl = fs, kind, caller, reason, model = null, durationMs = 0 }
|
|
93
|
+
function appendMiss({ roots, now = () => new Date(), fsImpl = fs, kind, caller, reason, model = null, durationMs = 0 } = {}) {
|
|
94
94
|
try {
|
|
95
95
|
const at = timestamp(now);
|
|
96
96
|
if (!at) return null;
|
|
@@ -103,7 +103,7 @@ function appendMiss({ roots, now = () => new Date(), fsImpl = fs, kind, caller,
|
|
|
103
103
|
} catch { return null; }
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
function appendOutcome({ roots, now = () => new Date(), fsImpl = fs, pickId, actual, source, store }
|
|
106
|
+
function appendOutcome({ roots, now = () => new Date(), fsImpl = fs, pickId, actual, source, store } = {}) {
|
|
107
107
|
try {
|
|
108
108
|
const at = timestamp(now);
|
|
109
109
|
if (!at) return null;
|
|
@@ -112,36 +112,36 @@ function appendOutcome({ roots, now = () => new Date(), fsImpl = fs, pickId, act
|
|
|
112
112
|
} catch { return null; }
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
function cutoffDate(now
|
|
115
|
+
function cutoffDate(now , maxAgeDays = RETAINED_DAYS) {
|
|
116
116
|
const at = timestamp(now);
|
|
117
117
|
if (!at) return null;
|
|
118
118
|
const cutoff = new Date(Date.parse(at) - maxAgeDays * 24 * 60 * 60 * 1000);
|
|
119
119
|
return cutoff.toISOString().slice(0, 10);
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
function partitionFiles(root
|
|
122
|
+
function partitionFiles(root , cutoff , fsImpl ) {
|
|
123
123
|
if (!root) return [];
|
|
124
124
|
const directory = path.join(root, 'advice', 'picks');
|
|
125
125
|
try {
|
|
126
126
|
return fsImpl.readdirSync(directory)
|
|
127
|
-
.filter((name
|
|
127
|
+
.filter((name ) => PARTITION_NAME.test(name) && name.slice(0, 10) >= cutoff)
|
|
128
128
|
.sort()
|
|
129
|
-
.map((name
|
|
130
|
-
} catch (error
|
|
129
|
+
.map((name ) => path.join(directory, name));
|
|
130
|
+
} catch (error ) {
|
|
131
131
|
if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR') return [];
|
|
132
132
|
return [{ unreadable: true, file: directory }];
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
function readWindow({ roots, now = () => new Date(), maxAgeDays = RETAINED_DAYS, fsImpl = fs }
|
|
136
|
+
function readWindow({ roots, now = () => new Date(), maxAgeDays = RETAINED_DAYS, fsImpl = fs } = {}) {
|
|
137
137
|
const cutoff = cutoffDate(now, maxAgeDays);
|
|
138
138
|
if (!cutoff) return { records: [], unreadable: 0 };
|
|
139
|
-
const records
|
|
139
|
+
const records = [];
|
|
140
140
|
let unreadable = 0;
|
|
141
141
|
for (const store of ['project-state', 'user-state']) {
|
|
142
142
|
for (const item of partitionFiles(roots?.[store], cutoff, fsImpl)) {
|
|
143
143
|
if (typeof item !== 'string') { unreadable += 1; continue; }
|
|
144
|
-
let contents
|
|
144
|
+
let contents ;
|
|
145
145
|
try { contents = fsImpl.readFileSync(item, 'utf8'); } catch { unreadable += 1; continue; }
|
|
146
146
|
for (const line of contents.split(/\r?\n/)) {
|
|
147
147
|
if (!line.trim()) continue;
|
|
@@ -156,10 +156,10 @@ function readWindow({ roots, now = () => new Date(), maxAgeDays = RETAINED_DAYS,
|
|
|
156
156
|
return { records, unreadable };
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
function findPick({ pickId, roots, now = () => new Date(), fsImpl = fs }
|
|
159
|
+
function findPick({ pickId, roots, now = () => new Date(), fsImpl = fs } = {}) {
|
|
160
160
|
if (typeof pickId !== 'string') return null;
|
|
161
161
|
const window = readWindow({ roots, now, fsImpl });
|
|
162
|
-
const found = window.records.find((entry
|
|
162
|
+
const found = window.records.find((entry ) => entry.record.type === 'pick' && entry.record.pickId === pickId);
|
|
163
163
|
return found ? { pick: found.record, file: found.file, store: found.store } : null;
|
|
164
164
|
}
|
|
165
165
|
|
|
@@ -4,28 +4,28 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('node:fs');
|
|
6
6
|
const path = require('node:path');
|
|
7
|
-
const { REPO_ROOT } = require('../../helper/repo-root.
|
|
7
|
+
const { REPO_ROOT } = require('../../helper/repo-root.js');
|
|
8
8
|
|
|
9
9
|
const ADVICE_FILE = 'core/registry/advice.json';
|
|
10
10
|
const MODELS_FILE = 'core/registry/models.json';
|
|
11
11
|
const ADAPTER_DIR = 'src/runtime/advice/providers';
|
|
12
12
|
const TIER_KIND = 'tier';
|
|
13
13
|
const LIMIT_FIELDS = ['maxStateBytes', 'maxBodyBytes', 'minQuestions', 'maxQuestions', 'minOptions', 'maxOptions'];
|
|
14
|
-
const BAND_FIELDS
|
|
14
|
+
const BAND_FIELDS = {
|
|
15
15
|
choice: ['clearAt', 'splitWithin'],
|
|
16
16
|
yesno: ['clearAbove', 'clearBelow', 'splitFrom', 'splitTo'],
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
function isObject(value
|
|
19
|
+
function isObject(value ) {
|
|
20
20
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function addError(errors
|
|
23
|
+
function addError(errors , key , message ) {
|
|
24
24
|
errors.push(`${key} ${message}`);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
function validateAdviceRegistry(registry
|
|
28
|
-
const errors
|
|
27
|
+
function validateAdviceRegistry(registry , { repoRoot = REPO_ROOT, models, fsImpl = fs } = {}) {
|
|
28
|
+
const errors = [];
|
|
29
29
|
if (!isObject(registry)) return { ok: false, errors: ['registry must be an object'] };
|
|
30
30
|
|
|
31
31
|
if (registry.version !== 1) addError(errors, 'version', 'must be 1');
|
|
@@ -80,7 +80,7 @@ function validateAdviceRegistry(registry: any, { repoRoot = REPO_ROOT, models, f
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
const yesno = strength.yesno;
|
|
83
|
-
if (isObject(yesno) && ['clearBelow', 'splitFrom', 'splitTo', 'clearAbove'].every((field
|
|
83
|
+
if (isObject(yesno) && ['clearBelow', 'splitFrom', 'splitTo', 'clearAbove'].every((field ) => typeof yesno[field] === 'number')) {
|
|
84
84
|
if (!(yesno.clearBelow < yesno.splitFrom && yesno.splitFrom < yesno.splitTo && yesno.splitTo < yesno.clearAbove)) {
|
|
85
85
|
addError(errors, 'strength.yesno', 'bands must satisfy clearBelow < splitFrom < splitTo < clearAbove');
|
|
86
86
|
}
|
|
@@ -89,15 +89,15 @@ function validateAdviceRegistry(registry: any, { repoRoot = REPO_ROOT, models, f
|
|
|
89
89
|
|
|
90
90
|
if (!models) {
|
|
91
91
|
try { models = JSON.parse(fsImpl.readFileSync(path.join(repoRoot, MODELS_FILE), 'utf8')); }
|
|
92
|
-
catch (error
|
|
92
|
+
catch (error ) { addError(errors, MODELS_FILE, `could not be loaded: ${error.message}`); }
|
|
93
93
|
}
|
|
94
|
-
const modelProviders = Array.isArray(models?.providers) ? new Set(models.providers.map((provider
|
|
94
|
+
const modelProviders = Array.isArray(models?.providers) ? new Set(models.providers.map((provider ) => provider?.id).filter((id ) => typeof id === 'string')) : new Set();
|
|
95
95
|
|
|
96
96
|
if (!Array.isArray(registry.providers) || registry.providers.length === 0) {
|
|
97
97
|
addError(errors, 'providers', 'must be a non-empty array');
|
|
98
98
|
} else {
|
|
99
99
|
const adapterRoot = path.resolve(repoRoot, ADAPTER_DIR);
|
|
100
|
-
registry.providers.forEach((provider
|
|
100
|
+
registry.providers.forEach((provider , index ) => {
|
|
101
101
|
const base = `providers[${index}]`;
|
|
102
102
|
if (!isObject(provider)) { addError(errors, base, 'must be an object'); return; }
|
|
103
103
|
for (const field of ['id', 'adapter', 'modelProvider', 'defaultEndpoint', 'route', 'minVersion', 'capability']) {
|
|
@@ -132,17 +132,17 @@ function validateAdviceRegistry(registry: any, { repoRoot = REPO_ROOT, models, f
|
|
|
132
132
|
if (!['choice', 'yesno'].includes(kind.type)) addError(errors, `${base}.type`, 'must be choice or yesno');
|
|
133
133
|
if (typeof kind.instructions !== 'string' || !kind.instructions.trim()) addError(errors, `${base}.instructions`, 'must be a non-empty string');
|
|
134
134
|
if (Object.hasOwn(kind, 'criteria') === Object.hasOwn(kind, 'criteriaFrom')) addError(errors, base, 'must have exactly one of criteria or criteriaFrom');
|
|
135
|
-
if (Object.hasOwn(kind, 'criteria') && (!isObject(kind.criteria) || Object.values(kind.criteria).some((value
|
|
135
|
+
if (Object.hasOwn(kind, 'criteria') && (!isObject(kind.criteria) || Object.values(kind.criteria).some((value ) => typeof value !== 'string' || !value.trim()))) {
|
|
136
136
|
addError(errors, `${base}.criteria`, 'must map option ids to non-empty descriptions');
|
|
137
137
|
}
|
|
138
138
|
if (Object.hasOwn(kind, 'criteriaFrom') && (typeof kind.criteriaFrom !== 'string' || !kind.criteriaFrom.trim())) {
|
|
139
139
|
addError(errors, `${base}.criteriaFrom`, 'must be a non-empty string');
|
|
140
140
|
}
|
|
141
141
|
if (typeof kind.staticFrom !== 'string') addError(errors, `${base}.staticFrom`, 'must be a string');
|
|
142
|
-
if (!Array.isArray(kind.askedBy) || kind.askedBy.some((caller
|
|
142
|
+
if (!Array.isArray(kind.askedBy) || kind.askedBy.some((caller ) => typeof caller !== 'string')) addError(errors, `${base}.askedBy`, 'must be an array of strings');
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
const tier = registry.kinds.find((kind
|
|
145
|
+
const tier = registry.kinds.find((kind ) => kind?.id === TIER_KIND);
|
|
146
146
|
const dispatchTiers = models?.dispatchTiers?.tiers;
|
|
147
147
|
if (!tier || !isObject(tier.criteria) || !Array.isArray(dispatchTiers)
|
|
148
148
|
|| JSON.stringify(Object.keys(tier.criteria)) !== JSON.stringify(dispatchTiers)) {
|
|
@@ -153,13 +153,13 @@ function validateAdviceRegistry(registry: any, { repoRoot = REPO_ROOT, models, f
|
|
|
153
153
|
return { ok: errors.length === 0, errors };
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
function loadAdviceRegistry({ repoRoot = REPO_ROOT, file, fsImpl = fs }
|
|
156
|
+
function loadAdviceRegistry({ repoRoot = REPO_ROOT, file, fsImpl = fs } = {}) {
|
|
157
157
|
const root = path.resolve(repoRoot);
|
|
158
158
|
const target = file || path.join(root, ADVICE_FILE);
|
|
159
|
-
let registry
|
|
159
|
+
let registry ;
|
|
160
160
|
try {
|
|
161
161
|
registry = JSON.parse(fsImpl.readFileSync(target, 'utf8'));
|
|
162
|
-
} catch (error
|
|
162
|
+
} catch (error ) {
|
|
163
163
|
return { ok: false, reason: 'config-invalid', detail: `${target}: ${error.message}` };
|
|
164
164
|
}
|
|
165
165
|
const validation = validateAdviceRegistry(registry, { repoRoot: root, fsImpl });
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
4
|
|
|
5
|
-
function strengthOf(answer
|
|
5
|
+
function strengthOf(answer , bands ) {
|
|
6
6
|
const type = answer?.type;
|
|
7
7
|
const thresholds = bands?.[type] || bands;
|
|
8
8
|
const probabilities = answer?.probabilities;
|
|
@@ -11,9 +11,9 @@ function strengthOf(answer: any, bands: any): { word: string; between: string[]
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
if (type === 'choice') {
|
|
14
|
-
const ordered
|
|
14
|
+
const ordered = Object.entries(probabilities)
|
|
15
15
|
.filter(([, probability]) => typeof probability === 'number' && Number.isFinite(probability))
|
|
16
|
-
.sort((left
|
|
16
|
+
.sort((left , right ) => right[1] - left[1]);
|
|
17
17
|
if (!ordered.length) return { word: 'leaning', between: null };
|
|
18
18
|
if (ordered[0][1] >= thresholds.clearAt) return { word: 'clear', between: null };
|
|
19
19
|
if (ordered.length > 1) {
|
|
@@ -30,9 +30,9 @@ function strengthOf(answer: any, bands: any): { word: string; between: string[]
|
|
|
30
30
|
const choice = bands?.choice;
|
|
31
31
|
const criteria = answer.criteria;
|
|
32
32
|
if (!choice || !Array.isArray(criteria)) return { word: 'leaning', between: null };
|
|
33
|
-
const ordered = criteria.map((criterion
|
|
34
|
-
.filter(([, probability]
|
|
35
|
-
.sort((left
|
|
33
|
+
const ordered = criteria.map((criterion , index ) => [criterion, probabilities[String(index)]])
|
|
34
|
+
.filter(([, probability] ) => typeof probability === 'number' && Number.isFinite(probability))
|
|
35
|
+
.sort((left , right ) => right[1] - left[1]);
|
|
36
36
|
if (!ordered.length) return { word: 'leaning', between: null };
|
|
37
37
|
if (ordered[0][1] >= choice.clearAt) return { word: 'clear', between: null };
|
|
38
38
|
if (ordered.length > 1) {
|
|
@@ -56,7 +56,7 @@ function strengthOf(answer: any, bands: any): { word: string; between: string[]
|
|
|
56
56
|
return { word: 'leaning', between: null };
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
function strengthWords(strength
|
|
59
|
+
function strengthWords(strength ) {
|
|
60
60
|
if (strength?.word === 'split' && Array.isArray(strength.between) && strength.between.length === 2) {
|
|
61
61
|
return `split between ${strength.between[0]} and ${strength.between[1]}`;
|
|
62
62
|
}
|
|
@@ -12,53 +12,53 @@
|
|
|
12
12
|
* those inputs and prints the block; it writes nothing.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
const { REPO_ROOT } = require('../helper/repo-root.
|
|
16
|
-
const { loadRegistry } = require('../registry/index.
|
|
17
|
-
const { CapabilityRouter } = require('./capability-router.
|
|
18
|
-
const { probeProviders, HEALTHY, UNVERIFIED } = require('./health.
|
|
19
|
-
const { mcpReach } = require('./mcp-reach.
|
|
20
|
-
const { WorkflowOrchestrator } = require('./workflow-orchestrator.
|
|
21
|
-
const { resolveActiveFeature } = require('./feature-resolve.
|
|
22
|
-
const { finishRuntime, usageError, captureCaught } = require('./cli-result.
|
|
15
|
+
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
16
|
+
const { loadRegistry } = require('../registry/index.js');
|
|
17
|
+
const { CapabilityRouter } = require('./capability-router.js');
|
|
18
|
+
const { probeProviders, HEALTHY, UNVERIFIED } = require('./health.js');
|
|
19
|
+
const { mcpReach } = require('./mcp-reach.js');
|
|
20
|
+
const { WorkflowOrchestrator } = require('./workflow-orchestrator.js');
|
|
21
|
+
const { resolveActiveFeature } = require('./feature-resolve.js');
|
|
22
|
+
const { finishRuntime, usageError, captureCaught } = require('./cli-result.js');
|
|
23
23
|
const fs = require('node:fs');
|
|
24
24
|
const path = require('node:path');
|
|
25
|
-
const { loadAdviceRegistry } = require('./advice/registry.
|
|
26
|
-
const { loadStorageRegistry } = require('./storage/registry.
|
|
27
|
-
const { resolveRoots } = require('./storage/roots.
|
|
28
|
-
const { resolveSetting } = require('./advice/config.
|
|
29
|
-
const { materializeKind } = require('./advice/kinds.
|
|
30
|
-
const { createAdviceClient } = require('./advice/client.
|
|
31
|
-
const { strengthWords } = require('./advice/strength.
|
|
25
|
+
const { loadAdviceRegistry } = require('./advice/registry.js');
|
|
26
|
+
const { loadStorageRegistry } = require('./storage/registry.js');
|
|
27
|
+
const { resolveRoots } = require('./storage/roots.js');
|
|
28
|
+
const { resolveSetting } = require('./advice/config.js');
|
|
29
|
+
const { materializeKind } = require('./advice/kinds.js');
|
|
30
|
+
const { createAdviceClient } = require('./advice/client.js');
|
|
31
|
+
const { strengthWords } = require('./advice/strength.js');
|
|
32
32
|
|
|
33
33
|
const SCHEMA = 1;
|
|
34
34
|
const REACHABLE = new Set(['selected', 'configured']);
|
|
35
35
|
|
|
36
36
|
/** The state of `server` on a harness: selected or configured on any of its scopes wins; absent
|
|
37
37
|
* only when every scope says so from a complete view (IC-013); else unknown. */
|
|
38
|
-
function stateOn(reach
|
|
39
|
-
const rows = (reach?.harnesses || []).filter((row
|
|
40
|
-
if (rows.some((row
|
|
41
|
-
if (rows.some((row
|
|
42
|
-
const states = rows.map((row
|
|
38
|
+
function stateOn(reach , harnessId , server ) {
|
|
39
|
+
const rows = (reach?.harnesses || []).filter((row ) => row.id === harnessId);
|
|
40
|
+
if (rows.some((row ) => row.servers[server] === 'selected')) return 'selected';
|
|
41
|
+
if (rows.some((row ) => row.servers[server] === 'configured')) return 'configured';
|
|
42
|
+
const states = rows.map((row ) => row.servers[server] ?? 'unknown');
|
|
43
43
|
return states.length && !states.includes('unknown') ? states[0] : 'unknown';
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/** Whether `server` is selected or configured on the target harness, or on any harness without one. */
|
|
47
|
-
function hasServer(reach
|
|
47
|
+
function hasServer(reach , harnessId , server ) {
|
|
48
48
|
if (!reach) return false;
|
|
49
|
-
return harnessId ? REACHABLE.has(stateOn(reach, harnessId, server)) : reach.harnesses.some((row
|
|
49
|
+
return harnessId ? REACHABLE.has(stateOn(reach, harnessId, server)) : reach.harnesses.some((row ) => REACHABLE.has(row.servers[server]));
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/** The target harness: a registry id named by the caller, else the one harness installed at the
|
|
53
53
|
* project scope (the global scope when the project has none), else none. */
|
|
54
|
-
function chooseTarget({ registry, reach, harness, missing }
|
|
54
|
+
function chooseTarget({ registry, reach, harness, missing } ) {
|
|
55
55
|
if (harness) {
|
|
56
|
-
if (registry.harnesses.some((item
|
|
56
|
+
if (registry.harnesses.some((item ) => item.id === harness)) return harness;
|
|
57
57
|
missing.push(`harness '${harness}' is not a registry harness; ignored`);
|
|
58
58
|
}
|
|
59
59
|
const rows = reach?.harnesses || [];
|
|
60
60
|
for (const scope of ['project', 'global']) {
|
|
61
|
-
const ids = [...new Set(rows.filter((row
|
|
61
|
+
const ids = [...new Set(rows.filter((row ) => row.scope === scope).map((row ) => row.id))];
|
|
62
62
|
if (ids.length) return ids.length === 1 ? ids[0] : null;
|
|
63
63
|
}
|
|
64
64
|
return null;
|
|
@@ -66,7 +66,7 @@ function chooseTarget({ registry, reach, harness, missing }: any) {
|
|
|
66
66
|
|
|
67
67
|
/** Tier for a dispatch: the task line's tier when valid, else the owner's, else the default (IC-004).
|
|
68
68
|
* A registry without `dispatchTiers` (the key is optional) gives `standard` with a note. */
|
|
69
|
-
function resolveTier({ dispatchTiers, owner, tier, missing }
|
|
69
|
+
function resolveTier({ dispatchTiers, owner, tier, missing } ) {
|
|
70
70
|
if (!dispatchTiers) {
|
|
71
71
|
missing.push('models.json has no dispatchTiers; tier standard assumed');
|
|
72
72
|
return { tier: 'standard', source: 'default', note: 'models.json has no dispatchTiers' };
|
|
@@ -81,14 +81,14 @@ function resolveTier({ dispatchTiers, owner, tier, missing }: any) {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
/** The first provider of a capability whose probe status is `status`, in registry order, or null. */
|
|
84
|
-
function firstWith(router
|
|
85
|
-
return (router.capabilities[capabilityId]?.providers || []).find((provider
|
|
84
|
+
function firstWith(router , capabilityId , probes , status ) {
|
|
85
|
+
return (router.capabilities[capabilityId]?.providers || []).find((provider ) => probes[provider.id]?.status === status) || null;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/** What answers one route intent on this machine: a healthy provider of the primary capability,
|
|
89
89
|
* else of the route's fallback capabilities in order; failing those, a provider that has no probe
|
|
90
90
|
* (UNVERIFIED) in the same order; else nothing. An unhealthy provider is never chosen. */
|
|
91
|
-
function resolveRoute(router
|
|
91
|
+
function resolveRoute(router , route , probes ) {
|
|
92
92
|
const chain = [route.capability, ...(route.fallback || [])];
|
|
93
93
|
for (const status of [HEALTHY, UNVERIFIED]) {
|
|
94
94
|
for (const capability of chain) {
|
|
@@ -101,8 +101,8 @@ function resolveRoute(router: any, route: any, probes: any) {
|
|
|
101
101
|
return { capability: route.capability, provider: null, status: 'NONE', via: null };
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
const cell = (text
|
|
105
|
-
const trigger = (server
|
|
104
|
+
const cell = (text ) => String(text).replace(/\|/g, '\\|').replace(/\s+/g, ' ');
|
|
105
|
+
const trigger = (server ) => server.trigger.replace(/\.$/, '');
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
108
|
* @param {Object} input
|
|
@@ -117,18 +117,18 @@ const trigger = (server: any) => server.trigger.replace(/\.$/, '');
|
|
|
117
117
|
* @param {{feature:?string, runTaskId:?string, stageId:?string, skill:?string}|null} [input.stage]
|
|
118
118
|
* @returns {{markdown: string, record: Object}} the record is IC-018 without `tasks`
|
|
119
119
|
*/
|
|
120
|
-
function renderBriefTools({ registry, router, probes, reach, owner, tier, harness, headingLevel = 2, stage = null }
|
|
121
|
-
const missing
|
|
120
|
+
function renderBriefTools({ registry, router, probes, reach, owner, tier, harness, headingLevel = 2, stage = null } ) {
|
|
121
|
+
const missing = [];
|
|
122
122
|
const hashes = '#'.repeat(Number(headingLevel) === 3 ? 3 : 2);
|
|
123
123
|
const target = chooseTarget({ registry, reach, harness, missing });
|
|
124
|
-
const targetHarness = target ? registry.harnesses.find((item
|
|
124
|
+
const targetHarness = target ? registry.harnesses.find((item ) => item.id === target) : null;
|
|
125
125
|
const resolved = resolveTier({ dispatchTiers: registry.dispatchTiers, owner, tier, missing });
|
|
126
126
|
const sourceText = resolved.source === 'owner' ? `owner ${owner}`
|
|
127
127
|
: resolved.source === 'task' ? 'task line' : `default; ${resolved.note || `owner ${owner} has no tier`}`;
|
|
128
128
|
|
|
129
129
|
const tools = [];
|
|
130
130
|
const rows = [];
|
|
131
|
-
for (const [intent, route] of Object.entries
|
|
131
|
+
for (const [intent, route] of Object.entries (router.routes)) {
|
|
132
132
|
const found = resolveRoute(router, route, probes);
|
|
133
133
|
if (!found.provider) {
|
|
134
134
|
rows.push(`| ${cell(intent)} | no healthy provider and no fallback | - |`);
|
|
@@ -163,9 +163,9 @@ function renderBriefTools({ registry, router, probes, reach, owner, tier, harnes
|
|
|
163
163
|
|
|
164
164
|
const skill = stage?.skill || null;
|
|
165
165
|
const expectedServers = skill
|
|
166
|
-
? registry.mcp.filter((server
|
|
167
|
-
const expectedIds = new Set(expectedServers.map((server
|
|
168
|
-
const expected = expectedServers.map((server
|
|
166
|
+
? registry.mcp.filter((server ) => (server.steps || []).some((step ) => step.skill === skill && !step.expectedWhen)) : [];
|
|
167
|
+
const expectedIds = new Set(expectedServers.map((server ) => server.id));
|
|
168
|
+
const expected = expectedServers.map((server ) => {
|
|
169
169
|
let state;
|
|
170
170
|
if (target) state = stateOn(reach, target, server.id);
|
|
171
171
|
else state = reach ? reach.anyHarnessHas(server.id) ?? 'unknown' : 'unknown';
|
|
@@ -173,8 +173,8 @@ function renderBriefTools({ registry, router, probes, reach, owner, tier, harnes
|
|
|
173
173
|
: (state === true ? 'reachable' : state === false ? 'unreachable' : 'unknown');
|
|
174
174
|
return { server: server.id, reach: reachState, detail: target ? state : `any-harness:${state}` };
|
|
175
175
|
});
|
|
176
|
-
const also = registry.mcp.filter((server
|
|
177
|
-
const list = (servers
|
|
176
|
+
const also = registry.mcp.filter((server ) => !expectedIds.has(server.id) && hasServer(reach, target, server.id));
|
|
177
|
+
const list = (servers ) => (servers.length ? servers.map((server ) => `\`${server.id}\` (${trigger(server)})`).join('; ') : 'none');
|
|
178
178
|
const targetName = targetHarness ? targetHarness.displayName || targetHarness.id : 'any installed harness';
|
|
179
179
|
|
|
180
180
|
const lines = [
|
|
@@ -218,7 +218,7 @@ function renderBriefTools({ registry, router, probes, reach, owner, tier, harnes
|
|
|
218
218
|
tierSource: resolved.source,
|
|
219
219
|
harness: target,
|
|
220
220
|
expected,
|
|
221
|
-
optional: also.map((server
|
|
221
|
+
optional: also.map((server ) => server.id),
|
|
222
222
|
tools,
|
|
223
223
|
missing,
|
|
224
224
|
};
|
|
@@ -226,7 +226,7 @@ function renderBriefTools({ registry, router, probes, reach, owner, tier, harnes
|
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
/** The stage a feature's run is at, or null when there is no readable run or it is not at a stage. */
|
|
229
|
-
function currentStageOf({ repoRoot = REPO_ROOT, projectRoot, taskId }
|
|
229
|
+
function currentStageOf({ repoRoot = REPO_ROOT, projectRoot, taskId } ) {
|
|
230
230
|
try {
|
|
231
231
|
const run = new WorkflowOrchestrator({ repoRoot, projectRoot }).readRun(taskId);
|
|
232
232
|
const node = run && run.cursor < run.program.length ? run.program[run.cursor] : null;
|
|
@@ -235,7 +235,7 @@ function currentStageOf({ repoRoot = REPO_ROOT, projectRoot, taskId }: any) {
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
/** The feature (`--slug`, else the resolved one) and its current stage, with a note for what is missing. */
|
|
238
|
-
function stageFor({ repoRoot, projectRoot, slug, missing }
|
|
238
|
+
function stageFor({ repoRoot, projectRoot, slug, missing } ) {
|
|
239
239
|
let feature = slug || null;
|
|
240
240
|
if (!feature) {
|
|
241
241
|
const resolved = resolveActiveFeature({ projectRoot });
|
|
@@ -258,18 +258,18 @@ function stageFor({ repoRoot, projectRoot, slug, missing }: any) {
|
|
|
258
258
|
* @param {Function} [options.buildAdapters] builds the adapter registry `mcpReach` reads installs through
|
|
259
259
|
* @param {Function} [options.probe] injection seam for `probeProviders`
|
|
260
260
|
*/
|
|
261
|
-
function taskLinesForAdvice({ projectRoot, feature, tasks }
|
|
261
|
+
function taskLinesForAdvice({ projectRoot, feature, tasks } ) {
|
|
262
262
|
if (!feature || !Array.isArray(tasks) || tasks.length === 0) throw new Error('feature or task ids are unavailable');
|
|
263
263
|
const planPath = path.join(projectRoot, 'agent-docs', 'doflow', feature, 'plan.md');
|
|
264
264
|
const plan = fs.readFileSync(planPath, 'utf8');
|
|
265
265
|
return tasks.map((id) => {
|
|
266
|
-
const line = plan.split(/\r?\n/).find((candidate
|
|
266
|
+
const line = plan.split(/\r?\n/).find((candidate ) => candidate.match(/^- \[[ x]\] (\S+)/)?.[1] === id);
|
|
267
267
|
if (!line) throw new Error(`task '${id}' was not found in ${planPath}`);
|
|
268
268
|
return line;
|
|
269
269
|
});
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
-
function adviceRecord(result
|
|
272
|
+
function adviceRecord(result ) {
|
|
273
273
|
if (result?.status === 'answered' && result.answers?.[0]) {
|
|
274
274
|
const answer = result.answers[0];
|
|
275
275
|
return {
|
|
@@ -285,7 +285,7 @@ function adviceRecord(result: any) {
|
|
|
285
285
|
/**
|
|
286
286
|
* The tier renderer remains synchronous/pure; optional local advice is requested afterward.
|
|
287
287
|
*/
|
|
288
|
-
async function handleBriefToolsCommand({ owner, tier, harness, slug, headingLevel, tasks, json = false, repoRoot = REPO_ROOT, projectRoot = process.cwd(), buildAdapters = null, probe = probeProviders, adviceClient = null }
|
|
288
|
+
async function handleBriefToolsCommand({ owner, tier, harness, slug, headingLevel, tasks, json = false, repoRoot = REPO_ROOT, projectRoot = process.cwd(), buildAdapters = null, probe = probeProviders, adviceClient = null } = {}) {
|
|
289
289
|
if (!owner) return usageError('brief-tools', '--owner is required (the agent type the brief is for)', json);
|
|
290
290
|
const level = headingLevel === undefined ? 2 : Number(headingLevel);
|
|
291
291
|
if (level !== 2 && level !== 3) return usageError('brief-tools', `--heading-level must be 2 or 3, got '${headingLevel}'`, json);
|
|
@@ -299,7 +299,7 @@ async function handleBriefToolsCommand({ owner, tier, harness, slug, headingLeve
|
|
|
299
299
|
const stage = stageFor({ repoRoot, projectRoot, slug, missing: notes });
|
|
300
300
|
const rendered = renderBriefTools({ registry, router, probes, reach, owner, tier, harness, headingLevel: level, stage });
|
|
301
301
|
let markdown = rendered.markdown;
|
|
302
|
-
const record
|
|
302
|
+
const record = rendered.record;
|
|
303
303
|
record.missing.push(...notes);
|
|
304
304
|
|
|
305
305
|
const taskIds = Array.isArray(tasks) ? tasks : typeof tasks === 'string' ? tasks.split(',').filter(Boolean) : [];
|
|
@@ -325,14 +325,14 @@ async function handleBriefToolsCommand({ owner, tier, harness, slug, headingLeve
|
|
|
325
325
|
}
|
|
326
326
|
}
|
|
327
327
|
}
|
|
328
|
-
} catch (error
|
|
328
|
+
} catch (error ) {
|
|
329
329
|
record.advice = { kind: 'tier', status: 'unavailable', reason: error?.reason || 'config-invalid' };
|
|
330
330
|
}
|
|
331
331
|
}
|
|
332
332
|
if (json) console.log(JSON.stringify({ markdown, record }, null, 2));
|
|
333
333
|
else process.stdout.write(markdown);
|
|
334
334
|
return finishRuntime(0);
|
|
335
|
-
} catch (error
|
|
335
|
+
} catch (error ) {
|
|
336
336
|
captureCaught(error, 'brief-tools', 1);
|
|
337
337
|
console.error(`[ERROR] brief-tools: ${error.message}`);
|
|
338
338
|
return finishRuntime(1);
|