@khoavu882/doflow 2.0.0-beta.3 → 2.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/doflow.js +2 -2
- package/core/.antigravity-plugin/plugin.json +0 -1
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
- package/core/harnesses/claude/hooks/session-start.sh +5 -3
- package/core/harnesses/claude/hooks/stop-check.sh +5 -3
- package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
- package/core/harnesses/codex/hooks/session-start.sh +5 -3
- package/core/harnesses/codex/hooks/stop-check.sh +6 -5
- package/core/harnesses/codex/hooks/subagent-audit.sh +17 -6
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +7 -18
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +7 -18
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +7 -19
- package/core/harnesses/gemini/hooks/session-start.sh +5 -3
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +20 -5
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +20 -5
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +20 -5
- package/core/harnesses/kiro/hooks/session-start.sh +11 -5
- package/core/harnesses/kiro/hooks/stop-check.sh +21 -30
- package/core/harnesses/pi/extensions/doflow-guard.js +159 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -11
- package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
- package/core/harnesses/shared/hooks/bridge/bridge.sh +184 -0
- package/core/harnesses/shared/hooks/bridge/envelope.jq +60 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +62 -0
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +4 -2
- package/core/harnesses/shared/hooks/policies/contract.sh +75 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +39 -4
- package/core/harnesses/shared/hooks/policies/lib.sh +4 -2
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +46 -34
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +75 -25
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +67 -73
- package/core/harnesses/shared/hooks/policies/session-context.sh +37 -7
- package/core/harnesses/shared/hooks/policies/stop-check.sh +42 -15
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +35 -5
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +40 -7
- package/core/harnesses/shared/hooks/stream-hook-runner.js +69 -181
- package/core/registry/assets.json +19 -0
- package/core/registry/contracts.json +2 -2
- package/core/registry/harnesses.json +196 -10
- package/core/registry/lifecycle.json +2626 -7
- package/core/registry/storage.json +110 -83
- package/core/registry/verification.json +1 -1
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -2
- package/core/shared/scripts/doflow/bin/doflow-run +10 -6
- package/core/shared/skills/do-design/SKILL.md +6 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +11 -10
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +2 -0
- package/core/shared/skills/do-execute-plan/references/scaffold.md +2 -2
- package/core/shared/skills/do-implement/SKILL.md +3 -2
- package/core/shared/skills/do-plan/SKILL.md +7 -8
- package/package.json +4 -2
- package/src/adapters/antigravity/{index.ts → index.js} +141 -105
- package/src/adapters/claude/{index.ts → index.js} +63 -49
- package/src/adapters/claude/{mcp.ts → mcp.js} +16 -16
- package/src/adapters/codex/{agents.ts → agents.js} +24 -24
- package/src/adapters/codex/{config.ts → config.js} +49 -49
- package/src/adapters/codex/{hooks.ts → hooks.js} +62 -27
- package/src/adapters/codex/{index.ts → index.js} +148 -105
- package/src/adapters/codex/{mcp.ts → mcp.js} +38 -38
- package/src/adapters/codex/{native-projection.ts → native-projection.js} +5 -5
- package/src/adapters/copilot/{index.ts → index.js} +42 -42
- package/src/adapters/{copy-tree.ts → copy-tree.js} +74 -74
- package/src/adapters/gemini/{hooks.ts → hooks.js} +78 -37
- package/src/adapters/gemini/{index.ts → index.js} +96 -57
- package/src/adapters/{hook-commands.ts → hook-commands.js} +8 -8
- package/src/adapters/{index.ts → index.js} +13 -13
- package/src/adapters/kiro/{index.ts → index.js} +49 -35
- package/src/adapters/{mcp-entries.ts → mcp-entries.js} +55 -55
- package/src/adapters/opencode/{index.ts → index.js} +52 -52
- package/src/adapters/pi/hooks.js +119 -0
- package/src/adapters/pi/{index.ts → index.js} +64 -39
- package/src/adapters/pi/{json-members.ts → json-members.js} +23 -23
- package/src/adapters/pi/{mcp.ts → mcp.js} +70 -70
- package/src/adapters/{resolve-source.ts → resolve-source.js} +1 -1
- package/src/cli/commands/{install.ts → install.js} +25 -25
- package/src/cli/commands/{list-backups.ts → list-backups.js} +4 -4
- package/src/cli/commands/{reconcile.ts → reconcile.js} +19 -19
- package/src/cli/commands/{remove.ts → remove.js} +10 -10
- package/src/cli/commands/{rollback.ts → rollback.js} +17 -17
- package/src/cli/commands/{self-update.ts → self-update.js} +3 -3
- package/src/cli/commands/{status.ts → status.js} +24 -24
- package/src/cli/commands/{tools.ts → tools.js} +16 -16
- package/src/cli/commands/{update.ts → update.js} +20 -20
- package/src/cli/{index.ts → index.js} +43 -31
- package/src/cli/{runtime-commands.ts → runtime-commands.js} +97 -58
- package/src/cli/{shared.ts → shared.js} +40 -40
- package/src/helper/{bash-helper.ts → bash-helper.js} +7 -7
- package/src/helper/{git.ts → git.js} +2 -2
- package/src/helper/{guardrails.ts → guardrails.js} +10 -10
- package/src/helper/{harness-paths.ts → harness-paths.js} +24 -24
- package/src/helper/{marker-merge.ts → marker-merge.js} +4 -4
- package/src/helper/{prompt.ts → prompt.js} +5 -5
- package/src/helper/{repo-root.ts → repo-root.js} +2 -2
- package/src/helper/{settings-merge.ts → settings-merge.js} +27 -27
- package/src/helper/{settings-scope.ts → settings-scope.js} +3 -3
- package/src/helper/{toml.ts → toml.js} +48 -48
- package/src/install/{backup-set.ts → backup-set.js} +16 -16
- package/src/install/{backup.ts → backup.js} +65 -65
- package/src/install/{context.ts → context.js} +15 -15
- package/src/install/{manifest.ts → manifest.js} +27 -27
- package/src/install/{mcp.ts → mcp.js} +30 -30
- package/src/install/{paths.ts → paths.js} +15 -15
- package/src/install/{targets.ts → targets.js} +10 -10
- package/src/install/{tool-lifecycle.ts → tool-lifecycle.js} +20 -20
- package/src/lifecycle/{bash-availability.ts → bash-availability.js} +1 -1
- package/src/lifecycle/coverage.js +57 -0
- package/src/lifecycle/hook-step.js +61 -0
- package/src/lifecycle/{index.ts → index.js} +160 -146
- package/src/lifecycle/{mcp-index.ts → mcp-index.js} +1 -1
- package/src/lifecycle/{policies.ts → policies.js} +11 -11
- package/src/lifecycle/{view.ts → view.js} +28 -28
- package/src/registry/{index.ts → index.js} +195 -66
- package/src/release/build-package.js +91 -0
- package/src/release/changelog-section.js +70 -0
- package/src/release/{sync-plugin-versions.ts → sync-plugin-versions.js} +4 -4
- package/src/runtime/advice/{cli.ts → cli.js} +31 -31
- package/src/runtime/advice/{client.ts → client.js} +44 -44
- package/src/runtime/advice/{config.ts → config.js} +16 -16
- package/src/runtime/advice/{doctor.ts → doctor.js} +32 -32
- package/src/runtime/advice/{endpoint-guard.ts → endpoint-guard.js} +20 -20
- package/src/runtime/advice/{kinds.ts → kinds.js} +16 -16
- package/src/runtime/advice/providers/{ollama-systemone.ts → ollama-systemone.js} +21 -21
- package/src/runtime/advice/{record.ts → record.js} +19 -19
- package/src/runtime/advice/{registry.ts → registry.js} +16 -16
- package/src/runtime/advice/{strength.ts → strength.js} +7 -7
- package/src/runtime/{brief-tools.ts → brief-tools.js} +50 -50
- package/src/runtime/{c4-project.ts → c4-project.js} +27 -27
- package/src/runtime/candidate.js +505 -0
- package/src/runtime/{capability-router.ts → capability-router.js} +39 -39
- package/src/runtime/{checkouts.ts → checkouts.js} +21 -21
- package/src/runtime/{claims.ts → claims.js} +44 -35
- package/src/runtime/{cli-result.ts → cli-result.js} +15 -8
- package/src/runtime/{cli.ts → cli.js} +59 -292
- package/src/runtime/{command-detect.ts → command-detect.js} +47 -47
- package/src/runtime/context/budget.js +203 -0
- package/src/runtime/context/compose.js +1367 -0
- package/src/runtime/context/delivery-store.js +102 -0
- package/src/runtime/context/mandatory.js +594 -0
- package/src/runtime/context/pointer.js +142 -0
- package/src/runtime/context-pack.js +1277 -0
- package/src/runtime/{decision-register.ts → decision-register.js} +67 -67
- package/src/runtime/evidence-fields.js +39 -0
- package/src/runtime/{evidence-ledger.ts → evidence-ledger.js} +32 -25
- package/src/runtime/failure/{capture.ts → capture.js} +29 -29
- package/src/runtime/failure/{classifier.ts → classifier.js} +4 -4
- package/src/runtime/failure/{cli.ts → cli.js} +30 -30
- package/src/runtime/failure/{home.ts → home.js} +6 -6
- package/src/runtime/failure/{store.ts → store.js} +32 -32
- package/src/runtime/{feature-resolve.ts → feature-resolve.js} +6 -6
- package/src/runtime/{freshness.ts → freshness.js} +59 -22
- package/src/runtime/{health.ts → health.js} +72 -72
- package/src/runtime/{history-compactor.ts → history-compactor.js} +25 -25
- package/src/runtime/{implementation-gate.ts → implementation-gate.js} +30 -30
- package/src/runtime/inventory/{coverage.ts → coverage.js} +10 -10
- package/src/runtime/inventory/{identity.ts → identity.js} +2 -2
- package/src/runtime/inventory/{index.ts → index.js} +36 -36
- package/src/runtime/inventory/{precedence.ts → precedence.js} +2 -2
- package/src/runtime/inventory/{read-scopes.ts → read-scopes.js} +9 -9
- package/src/runtime/inventory/{siblings.ts → siblings.js} +15 -15
- package/src/runtime/knowledge/{chunker.ts → chunker.js} +12 -12
- package/src/runtime/knowledge/{hybrid.ts → hybrid.js} +17 -17
- package/src/runtime/knowledge/{index-store.ts → index-store.js} +23 -23
- package/src/runtime/knowledge/{retrieval.ts → retrieval.js} +27 -27
- package/src/runtime/{leak-scan.ts → leak-scan.js} +12 -12
- package/src/runtime/lifecycle/{cli.ts → cli.js} +32 -32
- package/src/runtime/lifecycle/{event-store.ts → event-store.js} +48 -48
- package/src/runtime/lifecycle/{fold.ts → fold.js} +41 -41
- package/src/runtime/lifecycle/{followup.ts → followup.js} +54 -54
- package/src/runtime/lifecycle/{goal.ts → goal.js} +50 -50
- package/src/runtime/lifecycle/{intent-writer.ts → intent-writer.js} +5 -5
- package/src/runtime/lifecycle/{overview.ts → overview.js} +47 -47
- package/src/runtime/lifecycle/{release.ts → release.js} +29 -29
- package/src/runtime/lifecycle/{report-store.ts → report-store.js} +25 -25
- package/src/runtime/lifecycle/{retention.ts → retention.js} +37 -37
- package/src/runtime/lifecycle/{root.ts → root.js} +9 -9
- package/src/runtime/lifecycle/{status.ts → status.js} +64 -64
- package/src/runtime/lifecycle/{store-upkeep.ts → store-upkeep.js} +46 -46
- package/src/runtime/{locator-resolve.ts → locator-resolve.js} +7 -7
- package/src/runtime/{mask.ts → mask.js} +22 -22
- package/src/runtime/{mcp-consultations.ts → mcp-consultations.js} +54 -54
- package/src/runtime/{mcp-reach.ts → mcp-reach.js} +24 -24
- package/src/runtime/{model-router.ts → model-router.js} +20 -20
- package/src/runtime/{outcome.ts → outcome.js} +27 -27
- package/src/runtime/{reach.ts → reach.js} +21 -21
- package/src/runtime/{readiness-record.ts → readiness-record.js} +12 -12
- package/src/runtime/{readiness.ts → readiness.js} +113 -43
- package/src/runtime/{recovery.ts → recovery.js} +15 -15
- package/src/runtime/{research-request.ts → research-request.js} +34 -34
- package/src/runtime/{retrieval-plan.ts → retrieval-plan.js} +453 -104
- package/src/runtime/retrieval-record.js +223 -0
- package/src/runtime/scaffold/{artifacts.ts → artifacts.js} +22 -19
- package/src/runtime/scaffold/{fingerprint.ts → fingerprint.js} +3 -3
- package/src/runtime/scaffold/{generate.ts → generate.js} +109 -109
- package/src/runtime/scaffold/{languages.ts → languages.js} +34 -34
- package/src/runtime/source-version.js +289 -0
- package/src/runtime/storage/{cli.ts → cli.js} +9 -9
- package/src/runtime/storage/{engine.ts → engine.js} +37 -37
- package/src/runtime/storage/{registry.ts → registry.js} +19 -19
- package/src/runtime/storage/{roots.ts → roots.js} +8 -8
- package/src/runtime/storage/{rules.ts → rules.js} +26 -26
- package/src/runtime/storage/{upkeep.ts → upkeep.js} +35 -35
- package/src/runtime/storage/{work-status.ts → work-status.js} +39 -39
- package/src/runtime/{task-attempts.ts → task-attempts.js} +62 -62
- package/src/runtime/{task-classifier.ts → task-classifier.js} +34 -34
- package/src/runtime/{task-scope.ts → task-scope.js} +40 -15
- package/src/runtime/{task-state.ts → task-state.js} +86 -21
- package/src/runtime/trace/{indicators.ts → indicators.js} +22 -22
- package/src/runtime/trace/{ledger.ts → ledger.js} +42 -42
- package/src/runtime/trace/{render.ts → render.js} +7 -7
- package/src/runtime/trace/{views.ts → views.js} +40 -40
- package/src/runtime/verification/{contract-runner.ts → contract-runner.js} +23 -23
- package/src/runtime/verification/{engine.ts → engine.js} +143 -143
- package/src/runtime/verification/{registry.ts → registry.js} +6 -6
- package/src/runtime/verification/{scope-bound.ts → scope-bound.js} +50 -50
- package/src/runtime/verification/{test-selection.ts → test-selection.js} +28 -28
- package/src/runtime/{workflow-engine.ts → workflow-engine.js} +50 -50
- package/src/runtime/{workflow-orchestrator.ts → workflow-orchestrator.js} +91 -91
- package/src/runtime/{worktree.ts → worktree.js} +31 -31
- package/src/state/{index.ts → index.js} +26 -26
- package/src/state/{lockfile.ts → lockfile.js} +20 -20
- package/src/state/{run-lock.ts → run-lock.js} +44 -44
- package/core/.antigravity-plugin/hooks.json +0 -39
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
- package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
- package/core/harnesses/shared/hooks/hooks.json +0 -47
- package/src/runtime/context-pack.ts +0 -492
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Build the JavaScript form published to npm in an isolated staging directory. The checkout stays
|
|
4
|
+
// TypeScript so local development and the source CLI keep using Node's native type stripping.
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { stripTypeScriptTypes } = require('node:module');
|
|
8
|
+
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
9
|
+
|
|
10
|
+
const TEXT_EXTENSIONS = new Set(['.js', '.json', '.sh', '.md', '.yaml', '.yml']);
|
|
11
|
+
|
|
12
|
+
function walkFiles(root ) {
|
|
13
|
+
const files = [];
|
|
14
|
+
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
15
|
+
const file = path.join(root, entry.name);
|
|
16
|
+
if (entry.isDirectory()) files.push(...walkFiles(file));
|
|
17
|
+
else if (entry.isFile()) files.push(file);
|
|
18
|
+
}
|
|
19
|
+
return files;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function buildPackage(outDir , { repoRoot = REPO_ROOT } = {}) {
|
|
23
|
+
if (typeof stripTypeScriptTypes !== 'function') {
|
|
24
|
+
throw new Error('Node does not provide node:module.stripTypeScriptTypes');
|
|
25
|
+
}
|
|
26
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
|
|
27
|
+
const output = path.resolve(outDir);
|
|
28
|
+
fs.rmSync(output, { recursive: true, force: true });
|
|
29
|
+
fs.mkdirSync(output, { recursive: true });
|
|
30
|
+
|
|
31
|
+
for (const rel of packageJson.files || []) {
|
|
32
|
+
const source = path.join(repoRoot, rel);
|
|
33
|
+
if (fs.existsSync(source)) fs.cpSync(source, path.join(output, rel), { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
for (const name of ['package.json', 'README.md', 'LICENSE', 'CHANGELOG.md']) {
|
|
36
|
+
const source = path.join(repoRoot, name);
|
|
37
|
+
if (fs.existsSync(source)) fs.copyFileSync(source, path.join(output, name));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const converted = new Set ();
|
|
41
|
+
for (const file of walkFiles(path.join(output, 'src'))) {
|
|
42
|
+
if (!file.endsWith('.ts') || file.endsWith('.d.ts')) continue;
|
|
43
|
+
const relative = path.relative(output, file).split(path.sep).join('/');
|
|
44
|
+
const jsRelative = relative.slice(0, -3) + '.js';
|
|
45
|
+
const code = fs.readFileSync(file, 'utf8');
|
|
46
|
+
fs.writeFileSync(path.join(output, jsRelative), stripTypeScriptTypes(code, { mode: 'strip' }));
|
|
47
|
+
fs.rmSync(file);
|
|
48
|
+
converted.add(relative);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for (const file of walkFiles(output)) {
|
|
52
|
+
if (!TEXT_EXTENSIONS.has(path.extname(file))) continue;
|
|
53
|
+
const relative = path.relative(output, file).split(path.sep).join('/');
|
|
54
|
+
let content = fs.readFileSync(file, 'utf8');
|
|
55
|
+
content = content.replace(/(['"`])((?:\.\.?\/)[^'"`\n]*?\.ts)\1/g, (whole , quote , specifier ) => {
|
|
56
|
+
const resolved = path.posix.normalize(path.posix.join(path.posix.dirname(relative), specifier));
|
|
57
|
+
if (!converted.has(resolved)) return whole;
|
|
58
|
+
return `${quote}${specifier.slice(0, -3)}.js${quote}`;
|
|
59
|
+
});
|
|
60
|
+
for (const rel of [...converted].sort((a, b) => b.length - a.length)) {
|
|
61
|
+
content = content.split(rel).join(rel.slice(0, -3) + '.js');
|
|
62
|
+
}
|
|
63
|
+
fs.writeFileSync(file, content);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const remaining = walkFiles(output);
|
|
67
|
+
const runtimeFiles = ['src', 'bin'].flatMap((directory) => {
|
|
68
|
+
const root = path.join(output, directory);
|
|
69
|
+
return fs.existsSync(root) ? walkFiles(root) : [];
|
|
70
|
+
});
|
|
71
|
+
const nonDeclarationTs = runtimeFiles.filter((file ) => file.endsWith('.ts') && !file.endsWith('.d.ts'));
|
|
72
|
+
if (nonDeclarationTs.length) throw new Error(`staged package retains TypeScript files: ${nonDeclarationTs.join(', ')}`);
|
|
73
|
+
for (const file of remaining) {
|
|
74
|
+
if (!TEXT_EXTENSIONS.has(path.extname(file))) continue;
|
|
75
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
76
|
+
const unresolved = [...converted].filter((rel) => content.includes(rel));
|
|
77
|
+
if (unresolved.length) throw new Error(`${path.relative(output, file)} still references converted TypeScript paths: ${unresolved.join(', ')}`);
|
|
78
|
+
}
|
|
79
|
+
return output;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (require.main === module) {
|
|
83
|
+
const outDir = process.argv[2];
|
|
84
|
+
if (!outDir) {
|
|
85
|
+
process.stderr.write('Usage: node src/release/build-package.js <outDir>\n');
|
|
86
|
+
process.exit(2);
|
|
87
|
+
}
|
|
88
|
+
buildPackage(outDir);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = { buildPackage };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
// ── One release version, one CHANGELOG section, one GitHub Release body ─────────────────────────
|
|
6
|
+
//
|
|
7
|
+
// `gh release create --generate-notes` lists merged pull requests, and this repository merges
|
|
8
|
+
// locally, so every Release the workflow created carried only a compare link. CHANGELOG.md is
|
|
9
|
+
// where the release text is already written (Keep a Changelog, one `## [X.Y.Z]` heading per
|
|
10
|
+
// version), so the release workflow extracts the tagged version's section from it and passes that
|
|
11
|
+
// as the Release body. A tag whose section is missing or empty fails the job instead of
|
|
12
|
+
// publishing an empty Release.
|
|
13
|
+
//
|
|
14
|
+
// Usage: node src/release/changelog-section.js <version|tag> [CHANGELOG path]
|
|
15
|
+
|
|
16
|
+
const fs = require('node:fs');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
19
|
+
|
|
20
|
+
function escapeRegExp(text ) {
|
|
21
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Returns the body of a version's section: the lines after its `## [version]` heading up to the
|
|
26
|
+
* next `## [` heading, trimmed. The closing `]` after the version makes 1.2.1 and 1.2.10 distinct.
|
|
27
|
+
* A leading `v` on the version is ignored. Returns null when the heading is absent or the body is
|
|
28
|
+
* empty.
|
|
29
|
+
* @param {string} changelogText
|
|
30
|
+
* @param {string} version
|
|
31
|
+
* @returns {string|null}
|
|
32
|
+
*/
|
|
33
|
+
function changelogSection(changelogText , version ) {
|
|
34
|
+
const wanted = String(version).trim().replace(/^v/, '');
|
|
35
|
+
if (wanted === '') return null;
|
|
36
|
+
const lines = changelogText.split(/\r?\n/);
|
|
37
|
+
const heading = new RegExp(`^##\\s+\\[${escapeRegExp(wanted)}\\](\\s|$)`);
|
|
38
|
+
const start = lines.findIndex((line) => heading.test(line));
|
|
39
|
+
if (start === -1) return null;
|
|
40
|
+
let end = lines.length;
|
|
41
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
42
|
+
if (/^##\s+\[/.test(lines[i])) { end = i; break; }
|
|
43
|
+
}
|
|
44
|
+
const body = lines.slice(start + 1, end).join('\n').trim();
|
|
45
|
+
return body === '' ? null : body;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (require.main === module) {
|
|
49
|
+
const version = process.argv[2];
|
|
50
|
+
const file = process.argv[3] || path.join(REPO_ROOT, 'CHANGELOG.md');
|
|
51
|
+
if (!version) {
|
|
52
|
+
console.error('usage: node src/release/changelog-section.js <version|tag> [CHANGELOG path]');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
let text ;
|
|
56
|
+
try {
|
|
57
|
+
text = fs.readFileSync(file, 'utf8');
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error(`cannot read ${file}: ${(err ).message}`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
const body = changelogSection(text, version);
|
|
63
|
+
if (body === null) {
|
|
64
|
+
console.error(`no non-empty CHANGELOG section for ${String(version).replace(/^v/, '')} in ${file}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
process.stdout.write(`${body}\n`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { changelogSection };
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
const fs = require('node:fs');
|
|
16
16
|
const path = require('node:path');
|
|
17
|
-
const { REPO_ROOT } = require('../helper/repo-root.
|
|
17
|
+
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
18
18
|
|
|
19
19
|
/** Every distribution manifest that repeats the release version, relative to the repo root. */
|
|
20
20
|
const PLUGIN_MANIFESTS = [
|
|
@@ -31,14 +31,14 @@ const PLUGIN_MANIFESTS = [
|
|
|
31
31
|
* @param {Object} [options.fsImpl]
|
|
32
32
|
* @returns {{version: string, updated: Array<string>, unchanged: Array<string>}}
|
|
33
33
|
*/
|
|
34
|
-
function syncPluginVersions({ repoRoot = REPO_ROOT, fsImpl = fs }
|
|
34
|
+
function syncPluginVersions({ repoRoot = REPO_ROOT, fsImpl = fs } = {}) {
|
|
35
35
|
const pkg = JSON.parse(fsImpl.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
|
|
36
36
|
if (typeof pkg.version !== 'string' || pkg.version.trim() === '') {
|
|
37
37
|
throw new Error('package.json declares no version to project');
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
const updated
|
|
41
|
-
const unchanged
|
|
40
|
+
const updated = [];
|
|
41
|
+
const unchanged = [];
|
|
42
42
|
for (const rel of PLUGIN_MANIFESTS) {
|
|
43
43
|
const file = path.join(repoRoot, rel);
|
|
44
44
|
const text = fsImpl.readFileSync(file, 'utf8');
|
|
@@ -4,19 +4,19 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('node:fs');
|
|
6
6
|
const path = require('node:path');
|
|
7
|
-
const { finishRuntime, usageError } = require('../cli-result.
|
|
8
|
-
const { REPO_ROOT } = require('../../helper/repo-root.
|
|
9
|
-
const { loadAdviceRegistry } = require('./registry.
|
|
10
|
-
const { loadStorageRegistry } = require('../storage/registry.
|
|
11
|
-
const { resolveRoots } = require('../storage/roots.
|
|
12
|
-
const { resolveSetting, writeSetting } = require('./config.
|
|
13
|
-
const { createAdviceClient } = require('./client.
|
|
14
|
-
const { materializeKind, validateQuestionSet, validateStaticAnswer } = require('./kinds.
|
|
15
|
-
const { findPick, appendOutcome } = require('./record.
|
|
16
|
-
const { strengthWords } = require('./strength.
|
|
7
|
+
const { finishRuntime, usageError } = require('../cli-result.js');
|
|
8
|
+
const { REPO_ROOT } = require('../../helper/repo-root.js');
|
|
9
|
+
const { loadAdviceRegistry } = require('./registry.js');
|
|
10
|
+
const { loadStorageRegistry } = require('../storage/registry.js');
|
|
11
|
+
const { resolveRoots } = require('../storage/roots.js');
|
|
12
|
+
const { resolveSetting, writeSetting } = require('./config.js');
|
|
13
|
+
const { createAdviceClient } = require('./client.js');
|
|
14
|
+
const { materializeKind, validateQuestionSet, validateStaticAnswer } = require('./kinds.js');
|
|
15
|
+
const { findPick, appendOutcome } = require('./record.js');
|
|
16
|
+
const { strengthWords } = require('./strength.js');
|
|
17
17
|
|
|
18
|
-
function loadSources(repoRoot
|
|
19
|
-
const read = (name
|
|
18
|
+
function loadSources(repoRoot ) {
|
|
19
|
+
const read = (name ) => JSON.parse(fs.readFileSync(path.join(repoRoot, 'core/registry', name), 'utf8'));
|
|
20
20
|
return {
|
|
21
21
|
workflows: read('workflows.json'),
|
|
22
22
|
verification: read('verification.json'),
|
|
@@ -24,7 +24,7 @@ function loadSources(repoRoot: any) {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
function loadRuntime({ projectRoot, global = false }
|
|
27
|
+
function loadRuntime({ projectRoot, global = false } = {}) {
|
|
28
28
|
const advice = loadAdviceRegistry({ repoRoot: REPO_ROOT });
|
|
29
29
|
const storage = loadStorageRegistry({ repoRoot: REPO_ROOT });
|
|
30
30
|
const roots = resolveRoots({ registry: storage, projectRoot, global });
|
|
@@ -33,7 +33,7 @@ function loadRuntime({ projectRoot, global = false }: any = {}) {
|
|
|
33
33
|
return { adviceRegistry, adviceError: advice.ok ? null : advice.detail, storage, roots, sources };
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function emitResult(result
|
|
36
|
+
function emitResult(result , { json, kind } ) {
|
|
37
37
|
if (result.reason === 'over-limit') {
|
|
38
38
|
return usageError('advise', result.detail || 'request exceeds an advice limit', json);
|
|
39
39
|
}
|
|
@@ -50,11 +50,11 @@ function emitResult(result: any, { json, kind }: any) {
|
|
|
50
50
|
return finishRuntime(0);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
function isPresent(value
|
|
53
|
+
function isPresent(value ) {
|
|
54
54
|
return value !== undefined && value !== null;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
function readState({ state, stateFile, json }
|
|
57
|
+
function readState({ state, stateFile, json } ) {
|
|
58
58
|
if (isPresent(state) === isPresent(stateFile)) {
|
|
59
59
|
usageError('advise', 'provide exactly one of --state or --state-file', json);
|
|
60
60
|
return null;
|
|
@@ -64,26 +64,26 @@ function readState({ state, stateFile, json }: any) {
|
|
|
64
64
|
const content = fs.readFileSync(stateFile, 'utf8');
|
|
65
65
|
if (!content.length) throw new Error('file is empty');
|
|
66
66
|
return content;
|
|
67
|
-
} catch (error
|
|
67
|
+
} catch (error ) {
|
|
68
68
|
usageError('advise', `cannot read state file ${stateFile}: ${error.message}`, json);
|
|
69
69
|
return null;
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
function readQuestions(file
|
|
73
|
+
function readQuestions(file , json ) {
|
|
74
74
|
try {
|
|
75
75
|
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
76
76
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) || !Array.isArray(parsed.questions)) {
|
|
77
77
|
throw new Error('expected an object with a questions array');
|
|
78
78
|
}
|
|
79
79
|
return parsed.questions;
|
|
80
|
-
} catch (error
|
|
80
|
+
} catch (error ) {
|
|
81
81
|
usageError('advise', `cannot read questions file ${file}: ${error.message}`, json);
|
|
82
82
|
return null;
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
function parseAskInput(options
|
|
86
|
+
function parseAskInput(options ) {
|
|
87
87
|
const hasKind = typeof options.kind === 'string' && options.kind.length > 0;
|
|
88
88
|
const hasQuestionsFile = isPresent(options.questionsFile);
|
|
89
89
|
if (hasKind === hasQuestionsFile) {
|
|
@@ -103,7 +103,7 @@ function parseAskInput(options: any) {
|
|
|
103
103
|
return { state, hasKind, hasQuestionsFile };
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
function endpointSource(userSetting
|
|
106
|
+
function endpointSource(userSetting , endpoint ) {
|
|
107
107
|
if (userSetting?.decidedBy?.user) {
|
|
108
108
|
try {
|
|
109
109
|
const file = JSON.parse(fs.readFileSync(userSetting.decidedBy.user, 'utf8'));
|
|
@@ -113,7 +113,7 @@ function endpointSource(userSetting: any, endpoint: any) {
|
|
|
113
113
|
return 'provider default';
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
function inspectFailure(inspected
|
|
116
|
+
function inspectFailure(inspected , { endpoint, model, provider } ) {
|
|
117
117
|
if (inspected.endpoint.state === 'refused') return inspected.endpoint.message;
|
|
118
118
|
if (inspected.endpoint.state !== 'reached') {
|
|
119
119
|
return `advise: Ollama did not answer at ${endpoint}; start it yourself and re-run. Nothing was written.`;
|
|
@@ -126,21 +126,21 @@ function inspectFailure(inspected: any, { endpoint, model, provider }: any) {
|
|
|
126
126
|
}
|
|
127
127
|
if (!inspected.model.decisionCapable) {
|
|
128
128
|
const capable = inspected.models
|
|
129
|
-
.filter((entry
|
|
130
|
-
.map((entry
|
|
129
|
+
.filter((entry ) => Array.isArray(entry.capabilities) && entry.capabilities.includes(provider.capability))
|
|
130
|
+
.map((entry ) => entry.name);
|
|
131
131
|
return `advise: model ${model} is not decision-capable; decision-capable here: ${capable.length ? capable.join(', ') : 'none'}. Nothing was written.`;
|
|
132
132
|
}
|
|
133
133
|
return null;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
function writeFailure(message
|
|
136
|
+
function writeFailure(message , json ) {
|
|
137
137
|
if (json) console.log(JSON.stringify({ ok: false, error: 'write-failed', message }, null, 2));
|
|
138
138
|
else if (message.startsWith('decision model: endpoint ') || message.startsWith('advise:')) console.error(message);
|
|
139
139
|
else console.error(`advise: ${message}`);
|
|
140
140
|
return finishRuntime(1);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
async function handleConfigure(options
|
|
143
|
+
async function handleConfigure(options , runtime ) {
|
|
144
144
|
const { adviceRegistry, adviceError, roots, sources } = runtime;
|
|
145
145
|
const { json, project, global, force } = options;
|
|
146
146
|
const hasModel = typeof options.model === 'string' && options.model.length > 0;
|
|
@@ -173,7 +173,7 @@ async function handleConfigure(options: any, runtime: any) {
|
|
|
173
173
|
return writeFailure('turn the decision model on at user level first; a project cannot enable it', json);
|
|
174
174
|
}
|
|
175
175
|
const providerId = userSetting.provider || 'ollama';
|
|
176
|
-
const provider = adviceRegistry.providers.find((entry
|
|
176
|
+
const provider = adviceRegistry.providers.find((entry ) => entry.id === providerId);
|
|
177
177
|
if (!provider) return writeFailure(`provider '${providerId}' is not in core/registry/advice.json`, json);
|
|
178
178
|
const endpoint = project
|
|
179
179
|
? userSetting.endpoint
|
|
@@ -197,7 +197,7 @@ async function handleConfigure(options: any, runtime: any) {
|
|
|
197
197
|
return finishRuntime(0);
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
async function handleAsk(options
|
|
200
|
+
async function handleAsk(options , runtime ) {
|
|
201
201
|
const parsed = parseAskInput(options);
|
|
202
202
|
if (!parsed) return finishRuntime(2);
|
|
203
203
|
const { adviceRegistry, adviceError, roots, sources } = runtime;
|
|
@@ -243,7 +243,7 @@ async function handleAsk(options: any, runtime: any) {
|
|
|
243
243
|
return emitResult(result, { json: options.json, kind });
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
function handleOutcome(options
|
|
246
|
+
function handleOutcome(options , runtime ) {
|
|
247
247
|
const { json } = options;
|
|
248
248
|
if (typeof options.pickId !== 'string' || !options.pickId) return usageError('advise', '--pick-id is required', json);
|
|
249
249
|
if (typeof options.actual !== 'string' || !options.actual) return usageError('advise', '--actual is required', json);
|
|
@@ -265,7 +265,7 @@ function handleOutcome(options: any, runtime: any) {
|
|
|
265
265
|
return finishRuntime(0);
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
async function handleAdviseCommand(options
|
|
268
|
+
async function handleAdviseCommand(options = {}) {
|
|
269
269
|
const action = options.actionGiven ? options.action : 'ask';
|
|
270
270
|
if (!['ask', 'outcome', 'configure'].includes(action)) return usageError('advise', `unknown action '${action}'`, options.json);
|
|
271
271
|
try {
|
|
@@ -275,7 +275,7 @@ async function handleAdviseCommand(options: any = {}) {
|
|
|
275
275
|
if (action === 'configure') return await handleConfigure(shaped, runtime);
|
|
276
276
|
if (action === 'outcome') return handleOutcome(shaped, runtime);
|
|
277
277
|
return await handleAsk(shaped, runtime);
|
|
278
|
-
} catch (error
|
|
278
|
+
} catch (error ) {
|
|
279
279
|
return writeFailure(error?.message || 'advice command failed', options.json);
|
|
280
280
|
}
|
|
281
281
|
}
|
|
@@ -5,27 +5,27 @@
|
|
|
5
5
|
const fs = require('node:fs');
|
|
6
6
|
const http = require('node:http');
|
|
7
7
|
const https = require('node:https');
|
|
8
|
-
const { resolveSetting } = require('./config.
|
|
9
|
-
const { checkEndpoint } = require('./endpoint-guard.
|
|
10
|
-
const { strengthOf } = require('./strength.
|
|
11
|
-
const { validateQuestionSet, validateStaticAnswer } = require('./kinds.
|
|
12
|
-
const { appendPick, appendMiss } = require('./record.
|
|
8
|
+
const { resolveSetting } = require('./config.js');
|
|
9
|
+
const { checkEndpoint } = require('./endpoint-guard.js');
|
|
10
|
+
const { strengthOf } = require('./strength.js');
|
|
11
|
+
const { validateQuestionSet, validateStaticAnswer } = require('./kinds.js');
|
|
12
|
+
const { appendPick, appendMiss } = require('./record.js');
|
|
13
13
|
|
|
14
14
|
// Keep this map literal: module reachability must be auditable without dynamic require paths.
|
|
15
|
-
const ADAPTERS
|
|
16
|
-
'ollama-systemone': require('./providers/ollama-systemone.
|
|
15
|
+
const ADAPTERS = Object.freeze({
|
|
16
|
+
'ollama-systemone': require('./providers/ollama-systemone.js'),
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
function durationSince(startedAt
|
|
19
|
+
function durationSince(startedAt ) {
|
|
20
20
|
return Math.max(0, Date.now() - startedAt);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function versionTuple(version
|
|
23
|
+
function versionTuple(version ) {
|
|
24
24
|
const match = typeof version === 'string' && version.match(/^v?(\d+)\.(\d+)\.(\d+)/);
|
|
25
25
|
return match ? match.slice(1).map(Number) : null;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function versionAtLeast(version
|
|
28
|
+
function versionAtLeast(version , minimum ) {
|
|
29
29
|
const actual = versionTuple(version);
|
|
30
30
|
const required = versionTuple(minimum);
|
|
31
31
|
if (!actual || !required) return false;
|
|
@@ -36,16 +36,16 @@ function versionAtLeast(version: any, minimum: any) {
|
|
|
36
36
|
return true;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
function networkReason(error
|
|
39
|
+
function networkReason(error ) {
|
|
40
40
|
return error?.code === 'ADVICE_TIMEOUT' || error?.code === 'ETIMEDOUT' || error?.code === 'ESOCKETTIMEDOUT'
|
|
41
41
|
? 'timeout' : 'unreachable';
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function requestAction({ endpoint, action, pinnedLookup, timeoutMs, httpModule = http, httpsModule = https }
|
|
45
|
-
return new Promise
|
|
46
|
-
let request
|
|
44
|
+
function requestAction({ endpoint, action, pinnedLookup, timeoutMs, httpModule = http, httpsModule = https } ) {
|
|
45
|
+
return new Promise ((resolve, reject) => {
|
|
46
|
+
let request ;
|
|
47
47
|
let settled = false;
|
|
48
|
-
const finish = (error
|
|
48
|
+
const finish = (error , response ) => {
|
|
49
49
|
if (settled) return;
|
|
50
50
|
settled = true;
|
|
51
51
|
clearTimeout(timer);
|
|
@@ -53,7 +53,7 @@ function requestAction({ endpoint, action, pinnedLookup, timeoutMs, httpModule =
|
|
|
53
53
|
else resolve(response);
|
|
54
54
|
};
|
|
55
55
|
const timer = setTimeout(() => {
|
|
56
|
-
const error
|
|
56
|
+
const error = new Error(`decision model request timed out after ${timeoutMs}ms`);
|
|
57
57
|
error.code = 'ADVICE_TIMEOUT';
|
|
58
58
|
request?.destroy(error);
|
|
59
59
|
finish(error);
|
|
@@ -78,25 +78,25 @@ function requestAction({ endpoint, action, pinnedLookup, timeoutMs, httpModule =
|
|
|
78
78
|
'content-length': Buffer.byteLength(body),
|
|
79
79
|
},
|
|
80
80
|
};
|
|
81
|
-
request = transport.request(options, (response
|
|
82
|
-
const chunks
|
|
83
|
-
response.on('data', (chunk
|
|
84
|
-
response.on('error', (error
|
|
81
|
+
request = transport.request(options, (response ) => {
|
|
82
|
+
const chunks = [];
|
|
83
|
+
response.on('data', (chunk ) => chunks.push(Buffer.from(chunk)));
|
|
84
|
+
response.on('error', (error ) => finish(error));
|
|
85
85
|
response.on('end', () => finish(null, {
|
|
86
86
|
status: response.statusCode || 0,
|
|
87
87
|
bodyText: Buffer.concat(chunks).toString('utf8'),
|
|
88
88
|
}));
|
|
89
89
|
});
|
|
90
|
-
request.on('error', (error
|
|
90
|
+
request.on('error', (error ) => finish(error));
|
|
91
91
|
if (body === undefined) request.end();
|
|
92
92
|
else request.end(body);
|
|
93
|
-
} catch (error
|
|
93
|
+
} catch (error ) {
|
|
94
94
|
finish(error);
|
|
95
95
|
}
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
function emptyAdviceResult(status
|
|
99
|
+
function emptyAdviceResult(status , reason , detail , setting , durationMs ) {
|
|
100
100
|
return {
|
|
101
101
|
status, reason, detail: detail ?? null,
|
|
102
102
|
provider: setting?.provider ?? null,
|
|
@@ -116,14 +116,14 @@ function createAdviceClient({
|
|
|
116
116
|
httpModule = http,
|
|
117
117
|
httpsModule = https,
|
|
118
118
|
clock = () => new Date(),
|
|
119
|
-
}
|
|
120
|
-
async function ask({ questionSet, kind, staticAnswer, caller, taskId = null, record = true }
|
|
119
|
+
} = {}) {
|
|
120
|
+
async function ask({ questionSet, kind, staticAnswer, caller, taskId = null, record = true } = {}) {
|
|
121
121
|
const startedAt = Date.now();
|
|
122
|
-
let setting
|
|
122
|
+
let setting = null;
|
|
123
123
|
const effectiveKind = typeof kind === 'string' && kind ? kind : 'adhoc';
|
|
124
124
|
const shouldRecord = record !== false;
|
|
125
125
|
|
|
126
|
-
function unavailable(reason
|
|
126
|
+
function unavailable(reason , detail ) {
|
|
127
127
|
const durationMs = durationSince(startedAt);
|
|
128
128
|
if (shouldRecord && setting?.state === 'on') {
|
|
129
129
|
try { appendMiss({ roots, now: clock, fsImpl, kind: effectiveKind, caller, reason, model: setting.model, durationMs }); }
|
|
@@ -138,12 +138,12 @@ function createAdviceClient({
|
|
|
138
138
|
return emptyAdviceResult('off', setting.reason, setting.notes?.[0] || null, setting, durationSince(startedAt));
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
const provider = adviceRegistry?.providers?.find((entry
|
|
141
|
+
const provider = adviceRegistry?.providers?.find((entry ) => entry.id === setting.provider);
|
|
142
142
|
const adapter = provider && ADAPTERS[provider.adapter];
|
|
143
143
|
if (!provider || !adapter) return unavailable('config-invalid', `provider adapter '${provider?.adapter || setting.provider}' is unavailable`);
|
|
144
144
|
|
|
145
145
|
if (effectiveKind !== 'adhoc') {
|
|
146
|
-
const namedKind = adviceRegistry.kinds?.find((entry
|
|
146
|
+
const namedKind = adviceRegistry.kinds?.find((entry ) => entry.id === effectiveKind);
|
|
147
147
|
if (!namedKind || questionSet?.questions?.length !== 1 || questionSet.questions[0]?.name !== effectiveKind) {
|
|
148
148
|
return unavailable('config-invalid', `question set does not match kind '${effectiveKind}'`);
|
|
149
149
|
}
|
|
@@ -185,20 +185,20 @@ function createAdviceClient({
|
|
|
185
185
|
httpModule,
|
|
186
186
|
httpsModule,
|
|
187
187
|
});
|
|
188
|
-
} catch (error
|
|
188
|
+
} catch (error ) {
|
|
189
189
|
const reason = networkReason(error);
|
|
190
190
|
return unavailable(reason, error.message);
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
let parsed;
|
|
194
194
|
try { parsed = adapter.parseReply(response.status, response.bodyText, questionSet); }
|
|
195
|
-
catch (error
|
|
195
|
+
catch (error ) { return unavailable('malformed-reply', error.message); }
|
|
196
196
|
if (!parsed?.ok) return unavailable(parsed?.reason || 'malformed-reply', parsed?.detail || 'reply could not be parsed');
|
|
197
197
|
|
|
198
|
-
const answers = parsed.answers.map((answer
|
|
198
|
+
const answers = parsed.answers.map((answer , index ) => {
|
|
199
199
|
const strength = strengthOf(answer, adviceRegistry.strength);
|
|
200
200
|
const answerStatic = questionSet.questions.length === 1 ? staticAnswer ?? null : null;
|
|
201
|
-
const result
|
|
201
|
+
const result = {
|
|
202
202
|
name: answer.name,
|
|
203
203
|
kind: effectiveKind,
|
|
204
204
|
type: answer.type,
|
|
@@ -237,19 +237,19 @@ function createAdviceClient({
|
|
|
237
237
|
provider: setting.provider, model: setting.model, endpoint: setting.endpoint,
|
|
238
238
|
decidedBy: setting.decidedBy, durationMs: durationSince(startedAt), answers,
|
|
239
239
|
};
|
|
240
|
-
} catch (error
|
|
240
|
+
} catch (error ) {
|
|
241
241
|
return unavailable('config-invalid', error?.message || 'advice request failed');
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
async function inspect({ endpoint, model, provider: providerId = 'ollama', file, minVersion, timeoutMs }
|
|
246
|
-
const unavailable = (state
|
|
245
|
+
async function inspect({ endpoint, model, provider: providerId = 'ollama', file, minVersion, timeoutMs } = {}) {
|
|
246
|
+
const unavailable = (state , message , version = null, versionOk = false) => ({
|
|
247
247
|
endpoint: { state, version, versionOk, message: message ?? null },
|
|
248
248
|
models: [],
|
|
249
249
|
model: { installed: false, decisionCapable: false },
|
|
250
250
|
});
|
|
251
251
|
try {
|
|
252
|
-
const provider = adviceRegistry?.providers?.find((entry
|
|
252
|
+
const provider = adviceRegistry?.providers?.find((entry ) => entry.id === providerId);
|
|
253
253
|
const adapter = provider && ADAPTERS[provider.adapter];
|
|
254
254
|
if (!provider || !adapter) return unavailable('refused', `provider '${providerId}' is unavailable`);
|
|
255
255
|
const probeLimit = Number.isFinite(timeoutMs) ? timeoutMs : adviceRegistry.probeTimeoutMs;
|
|
@@ -258,7 +258,7 @@ function createAdviceClient({
|
|
|
258
258
|
const remaining = () => Math.max(0, budgetMs - (Date.now() - startedAt));
|
|
259
259
|
const endpointCheck = await checkEndpoint(endpoint, { lookup, file, timeoutMs: remaining() });
|
|
260
260
|
if (!endpointCheck.ok) return unavailable(endpointCheck.reason === 'timeout' ? 'unreachable' : 'refused', endpointCheck.message);
|
|
261
|
-
const requestProbe = (action
|
|
261
|
+
const requestProbe = (action ) => {
|
|
262
262
|
const left = remaining();
|
|
263
263
|
if (left <= 0) throw Object.assign(new Error('decision model probe budget exhausted'), { code: 'ADVICE_TIMEOUT' });
|
|
264
264
|
return requestAction({
|
|
@@ -269,7 +269,7 @@ function createAdviceClient({
|
|
|
269
269
|
|
|
270
270
|
let versionResponse;
|
|
271
271
|
try { versionResponse = await requestProbe(adapter.versionRequest()); }
|
|
272
|
-
catch (error
|
|
272
|
+
catch (error ) { return unavailable('unreachable', error.message); }
|
|
273
273
|
const version = adapter.parseVersion(versionResponse.status, versionResponse.bodyText)?.version ?? null;
|
|
274
274
|
const minimum = minVersion || provider.minVersion;
|
|
275
275
|
const versionOk = version ? versionAtLeast(version, minimum) : false;
|
|
@@ -288,9 +288,9 @@ function createAdviceClient({
|
|
|
288
288
|
return { endpoint: endpointResult, models, model: { installed: false, decisionCapable: false } };
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
const configuredIndex = models.findIndex((entry
|
|
292
|
-
const fallbackIndexes = models.map((entry
|
|
293
|
-
.filter((index
|
|
291
|
+
const configuredIndex = models.findIndex((entry ) => adapter.modelMatches(model, entry.name));
|
|
292
|
+
const fallbackIndexes = models.map((entry , index ) => index)
|
|
293
|
+
.filter((index ) => !Array.isArray(models[index].capabilities));
|
|
294
294
|
if (configuredIndex >= 0 && fallbackIndexes.includes(configuredIndex)) {
|
|
295
295
|
fallbackIndexes.splice(fallbackIndexes.indexOf(configuredIndex), 1);
|
|
296
296
|
fallbackIndexes.unshift(configuredIndex);
|
|
@@ -304,7 +304,7 @@ function createAdviceClient({
|
|
|
304
304
|
// Keep capabilities unknown when the read-only fallback is unavailable.
|
|
305
305
|
}
|
|
306
306
|
}
|
|
307
|
-
const match = models.find((entry
|
|
307
|
+
const match = models.find((entry ) => adapter.modelMatches(model, entry.name));
|
|
308
308
|
return {
|
|
309
309
|
endpoint: endpointResult,
|
|
310
310
|
models,
|
|
@@ -313,7 +313,7 @@ function createAdviceClient({
|
|
|
313
313
|
decisionCapable: Boolean(match?.capabilities?.includes(provider.capability)),
|
|
314
314
|
},
|
|
315
315
|
};
|
|
316
|
-
} catch (error
|
|
316
|
+
} catch (error ) {
|
|
317
317
|
return unavailable('unreachable', error?.message || 'decision model inspection failed');
|
|
318
318
|
}
|
|
319
319
|
}
|