@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
|
@@ -4,17 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('node:fs');
|
|
6
6
|
const path = require('node:path');
|
|
7
|
-
const { resolveRoots } = require('../storage/roots.
|
|
7
|
+
const { resolveRoots } = require('../storage/roots.js');
|
|
8
8
|
|
|
9
9
|
const SETTING_FILE = 'decision-model.json';
|
|
10
10
|
const PROJECT_NOTE = 'a project cannot enable the decision model or choose its endpoint';
|
|
11
11
|
const KNOWN_KEYS = new Set(['schema', 'enabled', 'provider', 'endpoint', 'model']);
|
|
12
12
|
|
|
13
|
-
function isObject(value
|
|
13
|
+
function isObject(value ) {
|
|
14
14
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function offResult(reason
|
|
17
|
+
function offResult(reason , decidedBy , notes = [], setting = {}) {
|
|
18
18
|
return {
|
|
19
19
|
state: 'off', reason,
|
|
20
20
|
provider: setting.provider ?? null,
|
|
@@ -26,24 +26,24 @@ function offResult(reason: string, decidedBy: any, notes: string[] = [], setting
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function readJson(file
|
|
29
|
+
function readJson(file , fsImpl ) {
|
|
30
30
|
if (!file) return { exists: false };
|
|
31
31
|
try {
|
|
32
32
|
return { exists: true, value: JSON.parse(fsImpl.readFileSync(file, 'utf8')) };
|
|
33
|
-
} catch (error
|
|
33
|
+
} catch (error ) {
|
|
34
34
|
if (error?.code === 'ENOENT') return { exists: false };
|
|
35
35
|
return { exists: true, error };
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
function parseUserSetting(value
|
|
39
|
+
function parseUserSetting(value , adviceRegistry ) {
|
|
40
40
|
if (!isObject(value)) return { error: 'user setting must be a JSON object' };
|
|
41
41
|
if (value.schema !== 1) return { error: 'user setting schema must be 1' };
|
|
42
42
|
if (typeof value.enabled !== 'boolean') return { error: 'user setting enabled must be a boolean' };
|
|
43
43
|
|
|
44
44
|
const providers = Array.isArray(adviceRegistry?.providers) ? adviceRegistry.providers : [];
|
|
45
45
|
const providerId = value.provider === undefined ? 'ollama' : value.provider;
|
|
46
|
-
const provider = providers.find((item
|
|
46
|
+
const provider = providers.find((item ) => item?.id === providerId);
|
|
47
47
|
if (typeof providerId !== 'string' || !provider) return { error: `user setting provider '${providerId}' is not in advice.json providers` };
|
|
48
48
|
|
|
49
49
|
let endpoint = provider.defaultEndpoint;
|
|
@@ -70,13 +70,13 @@ function parseUserSetting(value: any, adviceRegistry: any): any {
|
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
function parseProjectSetting(value
|
|
73
|
+
function parseProjectSetting(value ) {
|
|
74
74
|
if (!isObject(value)) return { error: 'project setting must be a JSON object' };
|
|
75
75
|
if (value.schema !== 1) return { error: 'project setting schema must be 1' };
|
|
76
76
|
if (value.enabled !== undefined && typeof value.enabled !== 'boolean') return { error: 'project setting enabled must be a boolean' };
|
|
77
77
|
if (value.model !== undefined && (typeof value.model !== 'string' || !value.model.trim())) return { error: 'project setting model must be a non-empty string' };
|
|
78
78
|
|
|
79
|
-
const notes
|
|
79
|
+
const notes = [];
|
|
80
80
|
if (value.enabled === true) notes.push(`project key 'enabled' ignored: ${PROJECT_NOTE}`);
|
|
81
81
|
for (const key of ['endpoint', 'provider']) {
|
|
82
82
|
if (Object.hasOwn(value, key)) notes.push(`project key '${key}' ignored: ${PROJECT_NOTE}`);
|
|
@@ -95,12 +95,12 @@ function resolveSetting({
|
|
|
95
95
|
global = false,
|
|
96
96
|
env = process.env,
|
|
97
97
|
fsImpl = fs,
|
|
98
|
-
}
|
|
99
|
-
const notes
|
|
98
|
+
} = {}) {
|
|
99
|
+
const notes = [];
|
|
100
100
|
let resolvedRoots = roots;
|
|
101
101
|
try {
|
|
102
102
|
if (!resolvedRoots) resolvedRoots = resolveRoots({ registry: storageRegistry, projectRoot, global, env });
|
|
103
|
-
} catch (error
|
|
103
|
+
} catch (error ) {
|
|
104
104
|
return offResult('config-invalid', { user: null, project: null }, [`storage roots could not be resolved: ${error.message}`]);
|
|
105
105
|
}
|
|
106
106
|
const userFile = resolvedRoots?.['config-home'] ? path.join(resolvedRoots['config-home'], SETTING_FILE) : null;
|
|
@@ -149,19 +149,19 @@ function resolveSetting({
|
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
function writeSetting({ file, setting, level = 'user', force = false, fsImpl = fs }
|
|
152
|
+
function writeSetting({ file, setting, level = 'user', force = false, fsImpl = fs } = {}) {
|
|
153
153
|
if (typeof file !== 'string' || !file || !isObject(setting)) {
|
|
154
154
|
return { ok: false, reason: 'config-invalid', detail: 'file and setting object are required' };
|
|
155
155
|
}
|
|
156
156
|
const directory = path.dirname(file);
|
|
157
|
-
let existing
|
|
157
|
+
let existing = {};
|
|
158
158
|
try {
|
|
159
159
|
if (fsImpl.existsSync(file)) {
|
|
160
160
|
try {
|
|
161
161
|
const parsed = JSON.parse(fsImpl.readFileSync(file, 'utf8'));
|
|
162
162
|
if (!isObject(parsed)) throw new Error('setting must be a JSON object');
|
|
163
163
|
existing = parsed;
|
|
164
|
-
} catch (error
|
|
164
|
+
} catch (error ) {
|
|
165
165
|
if (!force) return { ok: false, reason: 'config-invalid', detail: `${file}: ${error.message}` };
|
|
166
166
|
}
|
|
167
167
|
}
|
|
@@ -181,7 +181,7 @@ function writeSetting({ file, setting, level = 'user', force = false, fsImpl = f
|
|
|
181
181
|
try { if (fsImpl.existsSync(temporary)) fsImpl.unlinkSync(temporary); } catch { /* keep the primary result */ }
|
|
182
182
|
}
|
|
183
183
|
return { ok: true, file, setting: output };
|
|
184
|
-
} catch (error
|
|
184
|
+
} catch (error ) {
|
|
185
185
|
return { ok: false, reason: 'config-invalid', detail: `${file}: ${error.message}` };
|
|
186
186
|
}
|
|
187
187
|
}
|
|
@@ -3,64 +3,64 @@
|
|
|
3
3
|
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
4
|
|
|
5
5
|
const fs = require('node:fs');
|
|
6
|
-
const { resolveRoots } = require('../storage/roots.
|
|
7
|
-
const { loadStorageRegistry } = require('../storage/registry.
|
|
8
|
-
const { loadAdviceRegistry } = require('./registry.
|
|
9
|
-
const { resolveSetting } = require('./config.
|
|
10
|
-
const { createAdviceClient } = require('./client.
|
|
11
|
-
const { readWindow } = require('./record.
|
|
12
|
-
const { REPO_ROOT } = require('../../helper/repo-root.
|
|
6
|
+
const { resolveRoots } = require('../storage/roots.js');
|
|
7
|
+
const { loadStorageRegistry } = require('../storage/registry.js');
|
|
8
|
+
const { loadAdviceRegistry } = require('./registry.js');
|
|
9
|
+
const { resolveSetting } = require('./config.js');
|
|
10
|
+
const { createAdviceClient } = require('./client.js');
|
|
11
|
+
const { readWindow } = require('./record.js');
|
|
12
|
+
const { REPO_ROOT } = require('../../helper/repo-root.js');
|
|
13
13
|
|
|
14
14
|
const WINDOW_DAYS = 90;
|
|
15
|
-
const DONE_PHRASES
|
|
15
|
+
const DONE_PHRASES = Object.freeze({
|
|
16
16
|
'classify-verdict': 'accepted class',
|
|
17
17
|
'brief-record': 'tier the brief named',
|
|
18
18
|
caller: 'value the caller recorded',
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
function selectedStore(roots
|
|
21
|
+
function selectedStore(roots ) {
|
|
22
22
|
if (roots?.['project-state']) return { id: 'advice-picks-project', root: roots['project-state'] };
|
|
23
23
|
return { id: 'advice-picks-user', root: roots?.['user-state'] || null };
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function latestCall(records
|
|
27
|
-
const calls = records.filter(({ record }
|
|
26
|
+
function latestCall(records ) {
|
|
27
|
+
const calls = records.filter(({ record } ) => record.type === 'pick' || record.type === 'miss');
|
|
28
28
|
if (!calls.length) return null;
|
|
29
|
-
const record = calls.reduce((latest
|
|
29
|
+
const record = calls.reduce((latest , entry ) => !latest || String(entry.record.at).localeCompare(String(latest.record.at)) >= 0
|
|
30
30
|
? entry : latest).record;
|
|
31
31
|
return record.type === 'pick'
|
|
32
32
|
? { at: record.at, status: 'answered', reason: null }
|
|
33
33
|
: { at: record.at, status: 'unavailable', reason: record.reason || null };
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function agreementRows(registry
|
|
37
|
-
const picks = records.filter(({ record }
|
|
36
|
+
function agreementRows(registry , records ) {
|
|
37
|
+
const picks = records.filter(({ record } ) => record.type === 'pick');
|
|
38
38
|
const outcomes = new Map();
|
|
39
39
|
for (const { record } of records) {
|
|
40
40
|
if (record.type !== 'outcome' || typeof record.pickId !== 'string') continue;
|
|
41
41
|
const current = outcomes.get(record.pickId);
|
|
42
42
|
if (!current || String(record.at).localeCompare(String(current.at)) >= 0) outcomes.set(record.pickId, record);
|
|
43
43
|
}
|
|
44
|
-
const misses = records.filter(({ record }
|
|
45
|
-
const ids = [...(registry.kinds || []).map((kind
|
|
46
|
-
if (picks.some(({ record }
|
|
44
|
+
const misses = records.filter(({ record } ) => record.type === 'miss');
|
|
45
|
+
const ids = [...(registry.kinds || []).map((kind ) => kind.id)];
|
|
46
|
+
if (picks.some(({ record } ) => record.kind === 'adhoc')) ids.push('adhoc');
|
|
47
47
|
return ids.map((kind) => {
|
|
48
|
-
const kindPicks = picks.filter(({ record }
|
|
49
|
-
const completed = kindPicks.map(({ record }
|
|
50
|
-
.filter(({ outcome }
|
|
51
|
-
const sources = [...new Set(completed.map(({ outcome }
|
|
48
|
+
const kindPicks = picks.filter(({ record } ) => record.kind === kind);
|
|
49
|
+
const completed = kindPicks.map(({ record } ) => ({ pick: record.pick, outcome: outcomes.get(record.pickId) }))
|
|
50
|
+
.filter(({ outcome } ) => outcome);
|
|
51
|
+
const sources = [...new Set(completed.map(({ outcome } ) => outcome.source).filter((source ) => DONE_PHRASES[source]))];
|
|
52
52
|
return {
|
|
53
53
|
kind,
|
|
54
54
|
picks: kindPicks.length,
|
|
55
55
|
withOutcome: completed.length,
|
|
56
|
-
matched: completed.filter(({ pick, outcome }
|
|
57
|
-
unavailable: misses.filter(({ record }
|
|
56
|
+
matched: completed.filter(({ pick, outcome } ) => outcome.actual === pick).length,
|
|
57
|
+
unavailable: misses.filter(({ record } ) => record.kind === kind).length,
|
|
58
58
|
outcomeSources: sources,
|
|
59
59
|
};
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
function offReason(setting
|
|
63
|
+
function offReason(setting ) {
|
|
64
64
|
if (setting.reason === 'not-configured') return 'not configured';
|
|
65
65
|
if (setting.reason === 'config-invalid') return `unreadable setting in ${setting.decidedBy?.user || setting.decidedBy?.project || 'decision-model.json'}`;
|
|
66
66
|
if (setting.reason === 'project-disabled') return `switched off in ${setting.decidedBy?.project}`;
|
|
@@ -69,7 +69,7 @@ function offReason(setting: any) {
|
|
|
69
69
|
|
|
70
70
|
async function buildAdviceDoctor({ repoRoot = REPO_ROOT, projectRoot = process.cwd(), env = process.env, fsImpl = fs,
|
|
71
71
|
adviceRegistry: suppliedRegistry, storageRegistry: suppliedStorage, client: suppliedClient, lookup, httpModule, httpsModule,
|
|
72
|
-
probeTimeoutMs, doctorBudgetMs, now = () => new Date() }
|
|
72
|
+
probeTimeoutMs, doctorBudgetMs, now = () => new Date() } = {}) {
|
|
73
73
|
try {
|
|
74
74
|
const root = repoRoot;
|
|
75
75
|
const loaded = suppliedRegistry ? { ok: true, registry: suppliedRegistry } : loadAdviceRegistry({ repoRoot: root, fsImpl });
|
|
@@ -97,15 +97,15 @@ async function buildAdviceDoctor({ repoRoot = REPO_ROOT, projectRoot = process.c
|
|
|
97
97
|
doctorBudgetMs: doctorBudgetMs ?? registry.doctorBudgetMs };
|
|
98
98
|
const client = suppliedClient || createAdviceClient({ adviceRegistry: effectiveRegistry, roots, fsImpl, lookup, httpModule, httpsModule });
|
|
99
99
|
inspection = await client.inspect({ endpoint: setting.endpoint, model: setting.model, provider: setting.provider,
|
|
100
|
-
file: setting.decidedBy?.user, minVersion: registry.providers.find((p
|
|
100
|
+
file: setting.decidedBy?.user, minVersion: registry.providers.find((p ) => p.id === setting.provider)?.minVersion,
|
|
101
101
|
timeoutMs: probeTimeoutMs ?? registry.probeTimeoutMs });
|
|
102
|
-
const provider = registry.providers.find((p
|
|
102
|
+
const provider = registry.providers.find((p ) => p.id === setting.provider) || {};
|
|
103
103
|
decisionModel = {
|
|
104
104
|
setting: { state: 'on', reason: null, decidedBy: setting.decidedBy, modelFrom: setting.modelFrom, notes: setting.notes || [] },
|
|
105
105
|
endpoint: { url: setting.endpoint, state: inspection.endpoint.state, version: inspection.endpoint.version,
|
|
106
106
|
minVersion: provider.minVersion || null, versionOk: inspection.endpoint.versionOk, message: inspection.endpoint.message },
|
|
107
107
|
model: { name: setting.model, installed: inspection.model.installed, decisionCapable: inspection.model.decisionCapable },
|
|
108
|
-
decisionModels: inspection.models.filter((model
|
|
108
|
+
decisionModels: inspection.models.filter((model ) => model.capabilities?.includes(provider.capability)).map((model ) => model.name),
|
|
109
109
|
lastCall: latestCall(window.records),
|
|
110
110
|
};
|
|
111
111
|
} else {
|
|
@@ -117,7 +117,7 @@ async function buildAdviceDoctor({ repoRoot = REPO_ROOT, projectRoot = process.c
|
|
|
117
117
|
};
|
|
118
118
|
}
|
|
119
119
|
return { decisionModel, advice, _inspection: inspection, _offReason: setting.state === 'off' ? offReason(setting) : null };
|
|
120
|
-
} catch (error
|
|
120
|
+
} catch (error ) {
|
|
121
121
|
return { decisionModel: { setting: { state: 'off', reason: 'config-invalid', decidedBy: { user: null, project: null }, modelFrom: null, notes: [error.message] },
|
|
122
122
|
endpoint: { url: null, state: 'not-checked', version: null, minVersion: null, versionOk: false, message: null },
|
|
123
123
|
model: { name: null, installed: false, decisionCapable: false }, decisionModels: [], lastCall: null },
|
|
@@ -125,7 +125,7 @@ async function buildAdviceDoctor({ repoRoot = REPO_ROOT, projectRoot = process.c
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
function printAdviceDoctor(report
|
|
128
|
+
function printAdviceDoctor(report ) {
|
|
129
129
|
const decision = report.decisionModel;
|
|
130
130
|
const inspection = report._inspection;
|
|
131
131
|
console.log('\n[Decision Model] — advisory, never blocking');
|
|
@@ -156,10 +156,10 @@ function printAdviceDoctor(report: any) {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
console.log('\n[Advice Agreement] — picks kept for 90 days in this project; agreement with what was done, not correctness');
|
|
159
|
-
if (!report.advice.kinds.some((row
|
|
159
|
+
if (!report.advice.kinds.some((row ) => row.picks) && !report.advice.unreadableLines) console.log(' No picks recorded here.');
|
|
160
160
|
for (const row of report.advice.kinds) {
|
|
161
161
|
if (!row.picks) { console.log(` ${row.kind.padEnd(17)}no picks recorded`); continue; }
|
|
162
|
-
const phrases = row.outcomeSources.map((source
|
|
162
|
+
const phrases = row.outcomeSources.map((source ) => DONE_PHRASES[source]).filter(Boolean);
|
|
163
163
|
const done = phrases.length ? ` (done = ${phrases.join(' or ')})` : '';
|
|
164
164
|
console.log(` ${row.kind.padEnd(17)}${row.picks} picks, ${row.withOutcome} with a known outcome, matched ${row.matched} of ${row.withOutcome}${done}; ${row.unavailable} calls unavailable`);
|
|
165
165
|
}
|
|
@@ -4,17 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
const dns = require('node:dns');
|
|
6
6
|
|
|
7
|
-
function displayEndpoint(value
|
|
7
|
+
function displayEndpoint(value ) {
|
|
8
8
|
return typeof value === 'string' ? value : String(value ?? '');
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function ipv4Octets(address
|
|
11
|
+
function ipv4Octets(address ) {
|
|
12
12
|
if (typeof address !== 'string' || !/^\d{1,3}(?:\.\d{1,3}){3}$/.test(address)) return null;
|
|
13
13
|
const octets = address.split('.').map(Number);
|
|
14
14
|
return octets.every((octet) => octet >= 0 && octet <= 255) ? octets : null;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function ipv6Words(address
|
|
17
|
+
function ipv6Words(address ) {
|
|
18
18
|
if (typeof address !== 'string' || !address.includes(':')) return null;
|
|
19
19
|
let value = address.toLowerCase();
|
|
20
20
|
if (value.includes('.')) {
|
|
@@ -27,7 +27,7 @@ function ipv6Words(address: any): number[] | null {
|
|
|
27
27
|
}
|
|
28
28
|
const doubleColon = value.indexOf('::');
|
|
29
29
|
if (doubleColon !== -1 && value.indexOf('::', doubleColon + 2) !== -1) return null;
|
|
30
|
-
let words
|
|
30
|
+
let words ;
|
|
31
31
|
if (doubleColon === -1) {
|
|
32
32
|
words = value.split(':');
|
|
33
33
|
if (words.length !== 8) return null;
|
|
@@ -42,7 +42,7 @@ function ipv6Words(address: any): number[] | null {
|
|
|
42
42
|
return words.map((word) => Number.parseInt(word, 16));
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
function isLoopback(address
|
|
45
|
+
function isLoopback(address ) {
|
|
46
46
|
const octets = ipv4Octets(address);
|
|
47
47
|
if (octets) return octets[0] === 127;
|
|
48
48
|
const words = ipv6Words(address);
|
|
@@ -53,25 +53,25 @@ function isLoopback(address: any): boolean {
|
|
|
53
53
|
return (words[6] >> 8) === 127;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function literalFamily(address
|
|
56
|
+
function literalFamily(address ) {
|
|
57
57
|
return ipv4Octets(address) ? 4 : 6;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
function isLiteralIp(host
|
|
60
|
+
function isLiteralIp(host ) {
|
|
61
61
|
return Boolean(ipv4Octets(host) || ipv6Words(host));
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
function refuse(value
|
|
64
|
+
function refuse(value , file ) {
|
|
65
65
|
return {
|
|
66
66
|
ok: false,
|
|
67
67
|
message: `decision model: endpoint ${value} refused: only loopback addresses are allowed (127.0.0.0/8, ::1); set in ${file || 'provider default'}`,
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
function lookupAll(host
|
|
71
|
+
function lookupAll(host , lookup , timeoutMs ) {
|
|
72
72
|
return new Promise((resolve, reject) => {
|
|
73
73
|
let settled = false;
|
|
74
|
-
const finish = (error
|
|
74
|
+
const finish = (error , addresses ) => {
|
|
75
75
|
if (settled) return;
|
|
76
76
|
settled = true;
|
|
77
77
|
clearTimeout(timer);
|
|
@@ -80,10 +80,10 @@ function lookupAll(host: string, lookup: any, timeoutMs?: number): Promise<any>
|
|
|
80
80
|
};
|
|
81
81
|
const timer = Number.isFinite(timeoutMs) ? setTimeout(() => {
|
|
82
82
|
finish(Object.assign(new Error('decision model DNS validation timed out'), { code: 'ADVICE_TIMEOUT' }));
|
|
83
|
-
}, Math.max(0, timeoutMs
|
|
83
|
+
}, Math.max(0, timeoutMs )) : undefined;
|
|
84
84
|
try {
|
|
85
85
|
const returned = lookup(host, { all: true, verbatim: true }, finish);
|
|
86
|
-
if (returned && typeof returned.then === 'function') returned.then((addresses
|
|
86
|
+
if (returned && typeof returned.then === 'function') returned.then((addresses ) => finish(null, addresses), finish);
|
|
87
87
|
else if (Array.isArray(returned)) finish(null, returned);
|
|
88
88
|
} catch (error) {
|
|
89
89
|
finish(error);
|
|
@@ -91,8 +91,8 @@ function lookupAll(host: string, lookup: any, timeoutMs?: number): Promise<any>
|
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
function pinnedLookup(address
|
|
95
|
-
return (hostname
|
|
94
|
+
function pinnedLookup(address , family ) {
|
|
95
|
+
return (hostname , options , callback ) => {
|
|
96
96
|
if (typeof options === 'function') {
|
|
97
97
|
callback = options;
|
|
98
98
|
options = {};
|
|
@@ -103,9 +103,9 @@ function pinnedLookup(address: string, family: number) {
|
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
async function checkEndpoint(value
|
|
106
|
+
async function checkEndpoint(value , { lookup = dns.lookup, file, timeoutMs } = {}) {
|
|
107
107
|
const display = displayEndpoint(value);
|
|
108
|
-
let url
|
|
108
|
+
let url ;
|
|
109
109
|
try { url = new URL(display); } catch { return refuse(display, file); }
|
|
110
110
|
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) return refuse(display, file);
|
|
111
111
|
|
|
@@ -116,17 +116,17 @@ async function checkEndpoint(value: any, { lookup = dns.lookup, file, timeoutMs
|
|
|
116
116
|
return { ok: true, address: host, family, pinnedLookup: pinnedLookup(host, family) };
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
let addresses
|
|
120
|
-
try { addresses = await lookupAll(host, lookup, timeoutMs); } catch (error
|
|
119
|
+
let addresses ;
|
|
120
|
+
try { addresses = await lookupAll(host, lookup, timeoutMs); } catch (error ) {
|
|
121
121
|
if (error?.code === 'ADVICE_TIMEOUT') return { ok: false, reason: 'timeout', message: error.message };
|
|
122
122
|
return refuse(display, file);
|
|
123
123
|
}
|
|
124
124
|
if (!Array.isArray(addresses) || addresses.length === 0) return refuse(display, file);
|
|
125
|
-
const checked
|
|
125
|
+
const checked = addresses.map((entry ) => {
|
|
126
126
|
if (!entry || typeof entry.address !== 'string') return null;
|
|
127
127
|
return { address: entry.address, family: literalFamily(entry.address) };
|
|
128
128
|
});
|
|
129
|
-
if (checked.some((entry
|
|
129
|
+
if (checked.some((entry ) => !entry || !isLoopback(entry.address))) return refuse(display, file);
|
|
130
130
|
const first = checked[0];
|
|
131
131
|
return { ok: true, address: first.address, family: first.family, pinnedLookup: pinnedLookup(first.address, first.family) };
|
|
132
132
|
}
|
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
4
|
|
|
5
|
-
function isObject(value
|
|
5
|
+
function isObject(value ) {
|
|
6
6
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
function invalid(detail
|
|
9
|
+
function invalid(detail , reason = 'config-invalid') {
|
|
10
10
|
return { ok: false, reason, detail };
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
function optionCount(question
|
|
13
|
+
function optionCount(question ) {
|
|
14
14
|
if (question.type === 'score') return Array.isArray(question.criteria) ? question.criteria.length : 0;
|
|
15
15
|
return question.type === 'choice' ? Object.keys(question.options || {}).length : 2;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
function checkOptionRange(count
|
|
18
|
+
function checkOptionRange(count , limits , owner ) {
|
|
19
19
|
if (!Number.isInteger(limits?.minOptions) || !Number.isInteger(limits?.maxOptions)) {
|
|
20
20
|
return invalid(`${owner} cannot be built: invalid limits in core/registry/advice.json`);
|
|
21
21
|
}
|
|
@@ -28,16 +28,16 @@ function checkOptionRange(count: number, limits: any, owner: string): { ok: fals
|
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function criteriaFrom(source
|
|
32
|
-
let entries
|
|
31
|
+
function criteriaFrom(source , sources ) {
|
|
32
|
+
let entries ;
|
|
33
33
|
if (source === 'workflows.classes') {
|
|
34
34
|
const classes = sources?.workflows?.classes;
|
|
35
35
|
if (!isObject(classes)) return null;
|
|
36
|
-
entries = Object.entries(classes).map(([id, item]
|
|
36
|
+
entries = Object.entries(classes).map(([id, item] ) => [id, item?.description]);
|
|
37
37
|
} else if (source === 'verification.riskLevels') {
|
|
38
38
|
const levels = sources?.verification?.riskLevels;
|
|
39
39
|
if (!isObject(levels)) return null;
|
|
40
|
-
entries = Object.entries(levels).map(([id, item]
|
|
40
|
+
entries = Object.entries(levels).map(([id, item] ) => [id, item?.description]);
|
|
41
41
|
} else {
|
|
42
42
|
return null;
|
|
43
43
|
}
|
|
@@ -45,21 +45,21 @@ function criteriaFrom(source: string, sources: any): Record<string, any> | null
|
|
|
45
45
|
return Object.fromEntries(entries);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function materializeKind(kindId
|
|
48
|
+
function materializeKind(kindId , { adviceRegistry, sources = {}, server } = {}) {
|
|
49
49
|
const kinds = adviceRegistry?.kinds;
|
|
50
50
|
if (!Array.isArray(kinds)) return invalid('kinds are missing from core/registry/advice.json');
|
|
51
|
-
const kind = kinds.find((entry
|
|
51
|
+
const kind = kinds.find((entry ) => entry?.id === kindId);
|
|
52
52
|
if (!kind) return invalid(`unknown kind '${kindId}'`, 'usage-error');
|
|
53
53
|
|
|
54
54
|
const serverSource = kind.criteriaFrom === 'mcp.servers[].trigger';
|
|
55
55
|
if (server !== undefined && !serverSource) return invalid('--server is valid only for mcp-need', 'usage-error');
|
|
56
56
|
|
|
57
|
-
let question
|
|
57
|
+
let question ;
|
|
58
58
|
if (serverSource) {
|
|
59
59
|
if (typeof server !== 'string' || !server.trim()) return invalid('--server is required for mcp-need', 'usage-error');
|
|
60
60
|
const servers = sources?.mcp?.servers;
|
|
61
61
|
if (!Array.isArray(servers)) return invalid('mcp.servers is missing from core/registry/mcp.json');
|
|
62
|
-
const match = servers.find((entry
|
|
62
|
+
const match = servers.find((entry ) => entry?.id === server);
|
|
63
63
|
if (!match) return invalid(`server '${server}' is not present in core/registry/mcp.json`, 'usage-error');
|
|
64
64
|
if (typeof match.trigger !== 'string' || !match.trigger.trim()) return invalid(`mcp.servers.${server}.trigger is missing`);
|
|
65
65
|
question = { name: kind.id, type: 'yesno', instructions: match.trigger, no: kind.no, yes: kind.yes };
|
|
@@ -82,7 +82,7 @@ function materializeKind(kindId: string, { adviceRegistry, sources = {}, server
|
|
|
82
82
|
return { ok: true, kind: kind.id, question };
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
function validateQuestionSet(questionSet
|
|
85
|
+
function validateQuestionSet(questionSet , { adviceRegistry } = {}) {
|
|
86
86
|
const limits = adviceRegistry?.limits;
|
|
87
87
|
if (!isObject(limits)) return invalid('limits are missing from core/registry/advice.json');
|
|
88
88
|
if (!isObject(questionSet)) return invalid('question set must be an object (core/registry/advice.json limits.minQuestions)');
|
|
@@ -95,7 +95,7 @@ function validateQuestionSet(questionSet: any, { adviceRegistry }: any = {}): an
|
|
|
95
95
|
return invalid(`question count exceeds ${limits.maxQuestions} (core/registry/advice.json limits.maxQuestions)`);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
let namePattern
|
|
98
|
+
let namePattern ;
|
|
99
99
|
try { namePattern = new RegExp(limits.questionName); } catch {
|
|
100
100
|
return invalid('questionName does not compile (core/registry/advice.json limits.questionName)');
|
|
101
101
|
}
|
|
@@ -128,7 +128,7 @@ function validateQuestionSet(questionSet: any, { adviceRegistry }: any = {}): an
|
|
|
128
128
|
if (!Array.isArray(question.criteria)) return invalid(`${prefix}.criteria must be an array of criterion strings (core/registry/advice.json limits.minOptions)`);
|
|
129
129
|
const rangeError = checkOptionRange(question.criteria.length, limits, `${prefix}.criteria`);
|
|
130
130
|
if (rangeError) return rangeError;
|
|
131
|
-
if (question.criteria.some((criterion
|
|
131
|
+
if (question.criteria.some((criterion ) => typeof criterion !== 'string' || !criterion.trim())) {
|
|
132
132
|
return invalid(`${prefix}.criteria must be non-empty strings (core/registry/advice.json limits.minOptions)`);
|
|
133
133
|
}
|
|
134
134
|
if (new Set(question.criteria).size !== question.criteria.length) {
|
|
@@ -141,7 +141,7 @@ function validateQuestionSet(questionSet: any, { adviceRegistry }: any = {}): an
|
|
|
141
141
|
return { ok: true, questionSet };
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
function validateStaticAnswer(question
|
|
144
|
+
function validateStaticAnswer(question , value ) {
|
|
145
145
|
if (value === undefined || value === null) return { ok: true };
|
|
146
146
|
const validOptions = question?.type === 'choice'
|
|
147
147
|
? Object.keys(question.options || {})
|
|
@@ -7,7 +7,7 @@ const VERSION_PATH = '/api/version';
|
|
|
7
7
|
const TAGS_PATH = '/api/tags';
|
|
8
8
|
const SHOW_PATH = '/api/show';
|
|
9
9
|
|
|
10
|
-
function parseJson(bodyText
|
|
10
|
+
function parseJson(bodyText ) {
|
|
11
11
|
if (typeof bodyText !== 'string') return null;
|
|
12
12
|
try {
|
|
13
13
|
const value = JSON.parse(bodyText);
|
|
@@ -17,12 +17,12 @@ function parseJson(bodyText: any): any {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
function failure(reason
|
|
20
|
+
function failure(reason , detail ) {
|
|
21
21
|
return { ok: false, reason, detail };
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
function buildRequest(questionSet
|
|
25
|
-
const questions = Object.fromEntries(questionSet.questions.map((question
|
|
24
|
+
function buildRequest(questionSet , { model } = {}) {
|
|
25
|
+
const questions = Object.fromEntries(questionSet.questions.map((question ) => {
|
|
26
26
|
if (question.type === 'choice') {
|
|
27
27
|
return [question.name, {
|
|
28
28
|
type: 'choice',
|
|
@@ -52,19 +52,19 @@ function buildRequest(questionSet: any, { model }: any = {}): { method: string;
|
|
|
52
52
|
|
|
53
53
|
const SCORE_TOLERANCE = 1e-6;
|
|
54
54
|
|
|
55
|
-
function inUnit(value
|
|
55
|
+
function inUnit(value ) {
|
|
56
56
|
return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
function parseScore(question
|
|
60
|
-
const bad = (detail
|
|
59
|
+
function parseScore(question , value ) {
|
|
60
|
+
const bad = (detail ) => failure('malformed-reply', detail);
|
|
61
61
|
const { criteria, name } = question;
|
|
62
62
|
const last = criteria.length - 1;
|
|
63
63
|
if (value.type !== 'score') return bad(`answer for ${name} is not a score`);
|
|
64
|
-
const keys
|
|
65
|
-
const exact = (object
|
|
64
|
+
const keys = criteria.map((criterion , index ) => String(index));
|
|
65
|
+
const exact = (object ) => object && typeof object === 'object' && !Array.isArray(object)
|
|
66
66
|
&& Object.keys(object).length === keys.length && keys.every((key) => Object.hasOwn(object, key));
|
|
67
|
-
if (!exact(value.legend) || keys.some((key
|
|
67
|
+
if (!exact(value.legend) || keys.some((key , index ) => value.legend[key] !== criteria[index])) {
|
|
68
68
|
return bad(`legend for ${name} does not match its criteria`);
|
|
69
69
|
}
|
|
70
70
|
if (!exact(value.probabilities)) return bad(`probabilities for ${name} do not match its criteria`);
|
|
@@ -89,7 +89,7 @@ function parseScore(question: any, value: any): any {
|
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
function parseReply(status
|
|
92
|
+
function parseReply(status , bodyText , questionSet ) {
|
|
93
93
|
if (status === 400) {
|
|
94
94
|
const parsed = parseJson(bodyText);
|
|
95
95
|
return failure('endpoint-rejected', typeof parsed?.error === 'string' ? parsed.error : 'Ollama rejected the request');
|
|
@@ -108,7 +108,7 @@ function parseReply(status: number, bodyText: any, questionSet: any): any {
|
|
|
108
108
|
return failure('malformed-reply', 'reply has no answers object');
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
const answers
|
|
111
|
+
const answers = [];
|
|
112
112
|
for (const question of questionSet.questions) {
|
|
113
113
|
const value = parsed.answers[question.name];
|
|
114
114
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
@@ -159,7 +159,7 @@ function versionRequest() {
|
|
|
159
159
|
return { method: 'GET', path: VERSION_PATH };
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
function parseVersion(status
|
|
162
|
+
function parseVersion(status , bodyText ) {
|
|
163
163
|
const parsed = status === 200 ? parseJson(bodyText) : null;
|
|
164
164
|
return typeof parsed?.version === 'string' && parsed.version.trim() ? { version: parsed.version.trim() } : null;
|
|
165
165
|
}
|
|
@@ -168,31 +168,31 @@ function listRequest() {
|
|
|
168
168
|
return { method: 'GET', path: TAGS_PATH };
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
function parseList(status
|
|
171
|
+
function parseList(status , bodyText ) {
|
|
172
172
|
const parsed = status === 200 ? parseJson(bodyText) : null;
|
|
173
173
|
if (!Array.isArray(parsed?.models)) return [];
|
|
174
174
|
return parsed.models
|
|
175
|
-
.filter((model
|
|
176
|
-
.map((model
|
|
175
|
+
.filter((model ) => model && typeof model.name === 'string' && model.name.trim())
|
|
176
|
+
.map((model ) => ({
|
|
177
177
|
name: model.name,
|
|
178
|
-
capabilities: Array.isArray(model.capabilities) && model.capabilities.every((capability
|
|
178
|
+
capabilities: Array.isArray(model.capabilities) && model.capabilities.every((capability ) => typeof capability === 'string')
|
|
179
179
|
? model.capabilities : null,
|
|
180
180
|
}));
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
function showRequest(name
|
|
183
|
+
function showRequest(name ) {
|
|
184
184
|
return { method: 'POST', path: SHOW_PATH, body: { model: name } };
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
function parseShow(status
|
|
187
|
+
function parseShow(status , bodyText ) {
|
|
188
188
|
const parsed = status === 200 ? parseJson(bodyText) : null;
|
|
189
|
-
return Array.isArray(parsed?.capabilities) && parsed.capabilities.every((capability
|
|
189
|
+
return Array.isArray(parsed?.capabilities) && parsed.capabilities.every((capability ) => typeof capability === 'string')
|
|
190
190
|
? { capabilities: parsed.capabilities }
|
|
191
191
|
: null;
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
/** A configured name without a tag also matches Ollama's listed `<name>:latest` form. */
|
|
195
|
-
function modelMatches(configured
|
|
195
|
+
function modelMatches(configured , listed ) {
|
|
196
196
|
return configured === listed || (typeof configured === 'string' && !configured.includes(':') && listed === `${configured}:latest`);
|
|
197
197
|
}
|
|
198
198
|
|