@aiwg/cli 2026.10.5 → 2026.10.7
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/agentic/code/addons/composition-engine/lib/runtime.mjs +1401 -0
- package/agentic/code/addons/composition-engine/lib/validator.mjs +513 -0
- package/agentic/code/addons/composition-engine/schemas/flow-graph.schema.json +372 -0
- package/agentic/code/providers/model-capabilities.v1.json +74 -50
- package/bin/aiwg.mjs +7 -3
- package/dist/src/a2a/agent-card.js +11 -0
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/browser-export.js +1 -0
- package/dist/src/artifacts/citation-parser.js +8 -1
- package/dist/src/artifacts/cli.js +57 -6
- package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
- package/dist/src/artifacts/fortemi-core-sync.js +26 -8
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/contract.json +103 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-config.schema.json +212 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-set.schema.json +240 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-manifest.schema.json +451 -0
- package/dist/src/artifacts/fortemi-shard-export.js +284 -0
- package/dist/src/artifacts/graph-query.js +18 -8
- package/dist/src/artifacts/index-adapters.js +339 -0
- package/dist/src/artifacts/index-builder.js +143 -14
- package/dist/src/artifacts/index-files.js +15 -4
- package/dist/src/artifacts/index-preparation.js +2 -0
- package/dist/src/artifacts/index-reader.js +44 -0
- package/dist/src/artifacts/index-refresh.js +114 -55
- package/dist/src/artifacts/index-status.js +20 -5
- package/dist/src/artifacts/memory-maintenance.js +503 -0
- package/dist/src/artifacts/prebuilt-relations.js +3 -1
- package/dist/src/artifacts/query-engine.js +263 -108
- package/dist/src/artifacts/relations-builder.js +2 -2
- package/dist/src/artifacts/source-graph.js +2 -2
- package/dist/src/artifacts/types.js +9 -1
- package/dist/src/auth/client.js +5 -1
- package/dist/src/auth/credential-store.js +5 -1
- package/dist/src/auth/sensitive-input.js +151 -0
- package/dist/src/channel/manager.mjs +3 -0
- package/dist/src/cli/handlers/agentcard.js +47 -25
- package/dist/src/cli/handlers/auth.js +22 -2
- package/dist/src/cli/handlers/decision.js +22 -3
- package/dist/src/cli/handlers/effect.js +4 -84
- package/dist/src/cli/handlers/guided-setup.js +470 -0
- package/dist/src/cli/handlers/help.js +2 -2
- package/dist/src/cli/handlers/housekeeper.js +1110 -0
- package/dist/src/cli/handlers/index.js +4 -2
- package/dist/src/cli/handlers/init.js +41 -6
- package/dist/src/cli/handlers/install.js +6 -0
- package/dist/src/cli/handlers/local-executor.js +5 -3
- package/dist/src/cli/handlers/marketplace.js +15 -4
- package/dist/src/cli/handlers/mc.js +509 -53
- package/dist/src/cli/handlers/models.js +32 -2
- package/dist/src/cli/handlers/ralph-launcher.js +76 -4
- package/dist/src/cli/handlers/rules.js +1 -1
- package/dist/src/cli/handlers/script-runner.js +73 -2
- package/dist/src/cli/handlers/serve.js +5 -3
- package/dist/src/cli/handlers/sessions.js +1 -0
- package/dist/src/cli/handlers/setup.js +432 -1
- package/dist/src/cli/handlers/skill-lint.js +1 -1
- package/dist/src/cli/handlers/subcommands.js +118 -8
- package/dist/src/cli/handlers/use.js +335 -107
- package/dist/src/cli/handlers/workspace.js +3 -1
- package/dist/src/cli/prompt-utils.js +81 -0
- package/dist/src/cli/router.js +16 -1
- package/dist/src/config/aiwg-config.js +42 -0
- package/dist/src/config/cli.js +7 -3
- package/dist/src/config/decision-config.js +14 -1
- package/dist/src/config/gitignore.js +4 -0
- package/dist/src/config/native-sandbox-warnings.js +25 -0
- package/dist/src/config/workspace.js +2 -1
- package/dist/src/contributors/validation.js +6 -5
- package/dist/src/decision/adapters/jev.js +20 -31
- package/dist/src/decision/admission.js +7 -3
- package/dist/src/decision/ask.js +6 -1
- package/dist/src/decision/benchmark.js +482 -0
- package/dist/src/decision/ledger.js +22 -3
- package/dist/src/decision/pricing.js +147 -0
- package/dist/src/decision/qualification/gate-evidence.js +23 -18
- package/dist/src/decision/qualification/load-staged.js +576 -0
- package/dist/src/decision/qualification/load.js +109 -33
- package/dist/src/decision/qualification/runner.js +12 -5
- package/dist/src/decision/review/service.js +6 -4
- package/dist/src/decision/review/validate.js +23 -2
- package/dist/src/decision/savings.js +8 -3
- package/dist/src/discovery/index.js +492 -0
- package/dist/src/discovery/interview.js +238 -0
- package/dist/src/discovery/plan-apply.js +567 -0
- package/dist/src/discovery/plan-store.js +171 -0
- package/dist/src/discovery/plan-types.js +2 -0
- package/dist/src/discovery/plan-validation.js +139 -0
- package/dist/src/discovery/types.js +2 -0
- package/dist/src/effects/host.js +91 -0
- package/dist/src/extensions/commands/definitions.js +49 -9
- package/dist/src/extensions/manifest.js +71 -44
- package/dist/src/extensions/project-local-discovery.js +1 -1
- package/dist/src/extensions/project-local-gitignore.js +2 -0
- package/dist/src/extensions/project-quickref.js +18 -17
- package/dist/src/extensions/validation.js +32 -29
- package/dist/src/housekeeper/daemon-config.js +148 -0
- package/dist/src/housekeeper/ipc.js +84 -0
- package/dist/src/housekeeper/lifecycle.js +148 -0
- package/dist/src/housekeeper/platform.js +35 -0
- package/dist/src/housekeeper/status.js +70 -0
- package/dist/src/housekeeper/units.js +114 -0
- package/dist/src/issues/cli.js +16 -5
- package/dist/src/issues/live.js +44 -12
- package/dist/src/jobs/flow.js +22 -21
- package/dist/src/lint/rule-activation.js +20 -5
- package/dist/src/lint/runner.js +1 -1
- package/dist/src/marketplace/exchange.js +18 -6
- package/dist/src/marketplace/provenance.js +3 -2
- package/dist/src/mcp/adapters/codex-runtime.js +46 -76
- package/dist/src/mcp/cli.mjs +22 -96
- package/dist/src/mcp/config-file.mjs +6 -2
- package/dist/src/mcp/config-layers.mjs +23 -7
- package/dist/src/mcp/grok-build-config.mjs +2 -1
- package/dist/src/mcp/muse-native-config.mjs +2 -1
- package/dist/src/mcp/profiles.js +18 -31
- package/dist/src/mcp/profiles.mjs +18 -32
- package/dist/src/mcp/registry.js +7 -27
- package/dist/src/mcp/registry.mjs +7 -28
- package/dist/src/mcp/server.mjs +34 -1
- package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
- package/dist/src/mcp/tools/command-run.mjs +3 -2
- package/dist/src/mcp/tools/decision.mjs +49 -48
- package/dist/src/mcp/tools/discovery.mjs +46 -21
- package/dist/src/mcp/tools/orchestration.mjs +22 -22
- package/dist/src/mcp/tools/subsystems.mjs +9 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +30 -6
- package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
- package/dist/src/memory/audit.js +719 -0
- package/dist/src/memory/canonical-context.js +392 -13
- package/dist/src/memory/canonical-maintenance.js +95 -0
- package/dist/src/memory/cli-result.js +22 -0
- package/dist/src/memory/cli.js +176 -0
- package/dist/src/memory/context-pack.js +57 -6
- package/dist/src/memory/cost.js +512 -0
- package/dist/src/memory/firewall-classification.d.mts +7 -0
- package/dist/src/memory/firewall-classification.mjs +32 -0
- package/dist/src/memory/index.js +8 -0
- package/dist/src/memory/line-maintenance.js +68 -0
- package/dist/src/memory/maintenance-cli.js +286 -0
- package/dist/src/memory/maintenance-contract.js +136 -0
- package/dist/src/memory/maintenance-index.js +18 -0
- package/dist/src/memory/maintenance-owner.js +2 -0
- package/dist/src/memory/maintenance-record.js +138 -0
- package/dist/src/memory/maintenance.js +254 -0
- package/dist/src/memory/project-registry.js +2 -1
- package/dist/src/memory/provider-memory-adapter.js +154 -0
- package/dist/src/memory/recommend-cli.js +83 -0
- package/dist/src/memory/recommend.js +269 -0
- package/dist/src/memory/scheduled.js +81 -0
- package/dist/src/memory/taxonomy.js +44 -0
- package/dist/src/mission-protocol/codecs.js +19 -9
- package/dist/src/mission-protocol/worker-status.js +91 -1
- package/dist/src/models/config-loader.js +11 -1
- package/dist/src/models/model-capabilities.v1.json +74 -50
- package/dist/src/models/provider-policy.js +125 -62
- package/dist/src/models/resolver.js +45 -8
- package/dist/src/models/worker-installation.d.mts +8 -1
- package/dist/src/models/worker-installation.mjs +231 -38
- package/dist/src/models/wrapper-route.js +1 -1
- package/dist/src/output-modes/registry.js +8 -7
- package/dist/src/packages/adapters/git.js +99 -23
- package/dist/src/packages/adapters/gitea.js +8 -27
- package/dist/src/packages/adapters/github.js +7 -25
- package/dist/src/packages/registry.js +4 -4
- package/dist/src/plugin/metadata-validator.js +1 -1
- package/dist/src/process/cli-command.d.mts +5 -0
- package/dist/src/process/cli-command.mjs +42 -0
- package/dist/src/providers/deploy-version.d.mts +1 -0
- package/dist/src/providers/deploy-version.mjs +17 -0
- package/dist/src/providers/provider-definitions.js +19 -16
- package/dist/src/providers/skill-ownership.d.mts +11 -2
- package/dist/src/providers/skill-ownership.mjs +35 -17
- package/dist/src/rules/activation.d.mts +11 -2
- package/dist/src/rules/activation.mjs +82 -6
- package/dist/src/rules/inventory.js +1 -1
- package/dist/src/rules/rule-frontmatter.schema.json +2 -0
- package/dist/src/schema/zod-validation.d.mts +6 -0
- package/dist/src/schema/zod-validation.mjs +50 -0
- package/dist/src/serve/executor-registry.js +297 -20
- package/dist/src/serve/executor-token.d.mts +1 -0
- package/dist/src/serve/executor-token.mjs +8 -0
- package/dist/src/serve/shared-host-scheduler.js +1102 -31
- package/dist/src/serve/worker-invocation.js +124 -15
- package/dist/src/sessions/adapters/claude.js +19 -18
- package/dist/src/sessions/adapters/codex.js +10 -9
- package/dist/src/sessions/adapters/copilot.js +14 -13
- package/dist/src/sessions/adapters/cursor.js +19 -18
- package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
- package/dist/src/sessions/adapters/factory.js +11 -10
- package/dist/src/sessions/adapters/generic.js +32 -23
- package/dist/src/sessions/adapters/grokbot.js +35 -35
- package/dist/src/sessions/adapters/hermes.js +20 -19
- package/dist/src/sessions/adapters/muse.js +50 -49
- package/dist/src/sessions/adapters/omp.js +5 -4
- package/dist/src/sessions/adapters/openclaw.js +30 -29
- package/dist/src/sessions/adapters/opencode.js +26 -26
- package/dist/src/sessions/adapters/openhuman.js +24 -23
- package/dist/src/sessions/adapters/pi.js +5 -4
- package/dist/src/sessions/adapters/windsurf.js +8 -8
- package/dist/src/sessions/analytics.js +10 -9
- package/dist/src/sessions/candidates.js +7 -6
- package/dist/src/sessions/contracts.js +63 -53
- package/dist/src/sessions/output-registration.js +11 -10
- package/dist/src/sessions/workspace-discovery.js +3 -4
- package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
- package/dist/src/smiths/context-pipeline/claude-hook.js +46 -10
- package/dist/src/smiths/context-pipeline/context-file-safety.js +330 -9
- package/dist/src/smiths/context-pipeline/finalization.js +3 -2
- package/dist/src/smiths/context-pipeline/generator.js +7 -19
- package/dist/src/smiths/context-pipeline/legacy-inject.js +3 -10
- package/dist/src/smiths/context-pipeline/managed-hook.js +5 -5
- package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
- package/dist/src/smiths/context-pipeline/workspace-context.js +110 -22
- package/dist/src/tracker/project-inputs.js +2 -0
- package/dist/src/update/checker.d.mts +4 -0
- package/dist/src/update/checker.mjs +14 -0
- package/dist/src/update/notifier.mjs +2 -4
- package/dist/src/writing/exemplar-selection.js +6 -6
- package/dist/src/writing/voice-evaluation.js +24 -23
- package/dist/src/writing/writer-migration.js +14 -13
- package/dist/src/writing/writer-profile.js +19 -18
- package/dist/src/writing/writing-brief.js +18 -17
- package/dist/src/writing/writing-receipt.js +47 -46
- package/package.json +5 -2
- package/schemas/decision/DecisionReview.schema.json +78 -1
- package/schemas/mission-v1.schema.json +3 -0
- package/tools/agents/deploy-agents.mjs +67 -39
- package/tools/agents/providers/antigravity.mjs +6 -1
- package/tools/agents/providers/base.mjs +24 -14
- package/tools/agents/providers/claude.mjs +6 -3
- package/tools/agents/providers/codex.mjs +54 -57
- package/tools/agents/providers/context-write.mjs +28 -0
- package/tools/agents/providers/copilot.mjs +13 -4
- package/tools/agents/providers/cursor.mjs +1 -1
- package/tools/agents/providers/deepseek-harness.mjs +1 -1
- package/tools/agents/providers/factory.mjs +7 -1
- package/tools/agents/providers/grok-build.mjs +18 -2
- package/tools/agents/providers/grokbot.mjs +1 -1
- package/tools/agents/providers/hermes.mjs +12 -15
- package/tools/agents/providers/muse-hooks.mjs +21 -14
- package/tools/agents/providers/muse.mjs +7 -2
- package/tools/agents/providers/omp.mjs +31 -5
- package/tools/agents/providers/opencode.mjs +14 -5
- package/tools/agents/providers/openhuman.mjs +1 -1
- package/tools/agents/providers/pi.mjs +1 -1
- package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
- package/tools/agents/providers/rule-delivery.mjs +289 -80
- package/tools/agents/providers/warp.mjs +4 -1
- package/tools/agents/providers/windsurf.mjs +13 -3
- package/tools/cli/workspace-status.mjs +5 -0
- package/tools/commands/deploy-prompts-codex.mjs +11 -8
- package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +26 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +7 -1
- package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
- package/tools/ralph-external/lib/recovery-probe.mjs +14 -4
- package/tools/ralph-external/lib/worker-health.mjs +1 -1
- package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
- package/tools/ralph-external/process-monitor.mjs +49 -8
- package/tools/ralph-external/session-launcher.mjs +405 -35
- package/tools/skills/deploy-skills-codex.mjs +11 -7
- package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { getUserStateDir } from '../config/user-state.js';
|
|
5
|
+
import { projectAiwgPath, projectControlPath } from '../config/project-artifacts.js';
|
|
6
|
+
import { validateIndexConfig } from '../config/aiwg-config.js';
|
|
7
|
+
import { collectGraphIndexFiles, findArtifactFiles, INDEX_SCRATCH_DIRS } from './index-files.js';
|
|
8
|
+
import { writeIndexFile } from './index-reader.js';
|
|
9
|
+
import { GRAPH_CONFIGS, INDEX_VERSION, INDEX_EXTRACTOR_VERSION, getGraphIndexDir, resolveGraphBackendType, resolveGraphScanDir } from './types.js';
|
|
10
|
+
import { acquireDirectoryLock, DirectoryLockContentionError } from './prebuilt-build-lock.js';
|
|
11
|
+
import { resolveFrameworkIndexRoot } from './framework-index-paths.js';
|
|
12
|
+
import { resolveProjectLocalSearchRoots } from '../extensions/project-local-paths.js';
|
|
13
|
+
import { getFortemiCoreIndexDir } from './fortemi-core-sync.js';
|
|
14
|
+
function json(file) {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (error.code === 'ENOENT')
|
|
20
|
+
return undefined;
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function canonical(root) {
|
|
25
|
+
try {
|
|
26
|
+
return fs.realpathSync(root);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return path.resolve(root);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function hashFiles(files, context) {
|
|
33
|
+
const hash = createHash('sha256').update(JSON.stringify(context));
|
|
34
|
+
for (const file of [...new Set(files)].sort()) {
|
|
35
|
+
hash.update('\0').update(file).update('\0').update(fs.readFileSync(file));
|
|
36
|
+
}
|
|
37
|
+
return hash.digest('hex');
|
|
38
|
+
}
|
|
39
|
+
/** Same authorized source set as the builder, including links and bundle roots. */
|
|
40
|
+
export async function graphIndexDigest(cwd, graph, observation = {}) {
|
|
41
|
+
const fileStats = observation.fileStats ?? new Map();
|
|
42
|
+
const files = observation.files ? [...observation.files]
|
|
43
|
+
: graph === 'source' ? (await import('./source-graph.js')).collectSourceFiles(cwd, true)
|
|
44
|
+
: await collectGraphIndexFiles(cwd, graph, { selection: observation.selection, fileStats });
|
|
45
|
+
const ignoreRoots = graph && GRAPH_CONFIGS[graph]?.shared && graph !== 'framework' ? [] : [cwd];
|
|
46
|
+
if (graph)
|
|
47
|
+
ignoreRoots.push(...GRAPH_CONFIGS[graph].scanDirs.map(dir => resolveGraphScanDir(cwd, dir)));
|
|
48
|
+
for (const root of ignoreRoots) {
|
|
49
|
+
const ignoreFile = path.join(root, '.gitignore');
|
|
50
|
+
if (fs.existsSync(ignoreFile))
|
|
51
|
+
files.push(ignoreFile);
|
|
52
|
+
}
|
|
53
|
+
if (graph === 'source')
|
|
54
|
+
for (const file of ['tsconfig.json', 'package.json']) {
|
|
55
|
+
if (fs.existsSync(path.join(cwd, file)))
|
|
56
|
+
files.push(path.join(cwd, file));
|
|
57
|
+
}
|
|
58
|
+
// Supplements affect graph entries even though they are outside scanDirs.
|
|
59
|
+
for (const supplement of (graph ? GRAPH_CONFIGS[graph]?.metadataSupplements : undefined) ?? []) {
|
|
60
|
+
files.push(...findArtifactFiles(resolveGraphScanDir(cwd, supplement.scanDir)));
|
|
61
|
+
}
|
|
62
|
+
const inputs = [...new Set(files)].sort().map(file => {
|
|
63
|
+
const stat = fileStats.get(file) ?? fs.statSync(file);
|
|
64
|
+
return [file, stat.dev, stat.ino, stat.size, stat.mtimeMs, stat.ctimeMs];
|
|
65
|
+
});
|
|
66
|
+
return createHash('sha256').update(JSON.stringify([graph && GRAPH_CONFIGS[graph]?.shared ? null : canonical(cwd), INDEX_VERSION, INDEX_EXTRACTOR_VERSION,
|
|
67
|
+
graph ? GRAPH_CONFIGS[graph] : null, resolveGraphBackendType(graph), inputs])).digest('hex');
|
|
68
|
+
}
|
|
69
|
+
export function resolveFreshnessPolicy(cwd, adapter) {
|
|
70
|
+
const config = json(projectControlPath(cwd, 'aiwg.config'))?.index;
|
|
71
|
+
const errors = validateIndexConfig(config && { freshness: config.freshness });
|
|
72
|
+
if (errors.length)
|
|
73
|
+
throw new Error(errors.join('; '));
|
|
74
|
+
const defaults = {
|
|
75
|
+
graphs: 3_600_000, sessions: 900_000, caches: 86_400_000, runtime: 21_600_000,
|
|
76
|
+
models: 86_400_000, issues: 300_000, datasets: 86_400_000,
|
|
77
|
+
};
|
|
78
|
+
return { index: adapter.name, targetStaleAfterMs: defaults[adapter.group], maxRetriesPerDay: 3,
|
|
79
|
+
measureConvergence: true, ...config?.freshness?.[adapter.group], ...config?.freshness?.[adapter.name] };
|
|
80
|
+
}
|
|
81
|
+
/** Registry is bounded by graph registrations and existing owner scopes. No host discovery. */
|
|
82
|
+
export function indexAdapterRegistry(cwd) {
|
|
83
|
+
const adapters = [];
|
|
84
|
+
const add = (definition) => {
|
|
85
|
+
const adapter = { ...definition,
|
|
86
|
+
probe: (workspace, nowMs) => probeIndexAdapter(adapter, workspace, nowMs),
|
|
87
|
+
refresh: async (opts) => {
|
|
88
|
+
const { refreshIndexes } = await import('./index-refresh.js');
|
|
89
|
+
return refreshIndexes({ cwd: opts.cwd, only: adapter.name, full: opts.full,
|
|
90
|
+
dryRun: opts.dryRun, signal: opts.signal });
|
|
91
|
+
},
|
|
92
|
+
convergence: last => {
|
|
93
|
+
const jobs = last.jobs.filter(job => definition.group === 'graphs' ? job.kind === 'graph' && job.graph === definition.graph
|
|
94
|
+
: definition.group === 'caches' ? job.kind === 'cache' && job.graph === definition.graph : job.kind === definition.name);
|
|
95
|
+
return { converged: jobs.length > 0 && jobs.every(job => job.freshness?.status === 'fresh'
|
|
96
|
+
&& job.freshness.convergence?.converged !== false), wallMs: last.totals.wallMs,
|
|
97
|
+
residualJobs: jobs.filter(job => job.freshness?.status !== 'fresh').length };
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
adapters.push(adapter);
|
|
101
|
+
};
|
|
102
|
+
for (const [graph, config] of Object.entries(GRAPH_CONFIGS)) {
|
|
103
|
+
const directory = getGraphIndexDir(cwd, graph);
|
|
104
|
+
const outputs = ['metadata.json', 'dependencies.json', 'tags.json', 'stats.json'];
|
|
105
|
+
if (resolveGraphBackendType(graph) === 'sqlite')
|
|
106
|
+
outputs.push('graph.db');
|
|
107
|
+
if (graph === 'framework')
|
|
108
|
+
outputs.push('relations.json');
|
|
109
|
+
add({ name: graph, graph, group: 'graphs', identity: canonical(directory),
|
|
110
|
+
backend: resolveGraphBackendType(graph), operation: 'buildIndex', supported: true,
|
|
111
|
+
exclusions: [...INDEX_SCRATCH_DIRS, 'symlinks', 'gitignored', 'hidden directories'],
|
|
112
|
+
roots: workspace => [...new Set([
|
|
113
|
+
...config.scanDirs.map(dir => canonical(resolveGraphScanDir(graph === 'framework' ? resolveFrameworkIndexRoot(workspace) : workspace, dir))),
|
|
114
|
+
...(graph === 'project' ? [canonical(workspace), ...resolveProjectLocalSearchRoots(workspace).map(root => canonical(root.rootPath))] : []),
|
|
115
|
+
])],
|
|
116
|
+
outputPaths: outputs.map(file => path.join(directory, file)), statePath: path.join(directory, 'freshness.json') });
|
|
117
|
+
if (graph === 'framework') {
|
|
118
|
+
const owner = adapters[adapters.length - 1];
|
|
119
|
+
add({ ...owner, name: 'relationships', operation: 'buildIndex(framework):relations',
|
|
120
|
+
reason: 'Relationship projection shares the framework snapshot and owner lock' });
|
|
121
|
+
}
|
|
122
|
+
const cacheDirectory = getFortemiCoreIndexDir(cwd, graph);
|
|
123
|
+
if (fs.existsSync(cacheDirectory))
|
|
124
|
+
add({ name: `caches:${graph}`, graph, group: 'caches',
|
|
125
|
+
identity: canonical(cacheDirectory), backend: 'fortemi-core', operation: 'syncFortemiCoreIndex',
|
|
126
|
+
supported: true, exclusions: ['remote mutation'], roots: () => [canonical(directory)],
|
|
127
|
+
outputPaths: ['manifest.json', 'aiwg-fortemi-index-v2.json'].map(file => path.join(cacheDirectory, file)),
|
|
128
|
+
statePath: path.join(cacheDirectory, 'freshness.json') });
|
|
129
|
+
}
|
|
130
|
+
const scopes = [
|
|
131
|
+
{ name: 'sessions', directory: path.join(cwd, '.aiwg/sessions'), backend: 'sqlite',
|
|
132
|
+
operation: 'refreshSessionCatalog', outputs: ['catalog.sqlite'] },
|
|
133
|
+
{ name: 'runtime', directory: projectAiwgPath(cwd, 'smiths/toolsmith'), backend: 'json',
|
|
134
|
+
operation: 'RuntimeDiscovery', outputs: ['runtime.json', 'runtime-info.md'] },
|
|
135
|
+
{ name: 'issues', directory: projectAiwgPath(cwd, 'issues'), backend: 'json',
|
|
136
|
+
operation: 'LocalIssueProviderCore.rebuildIssueIndex', outputs: ['index/issues.index.json'] },
|
|
137
|
+
];
|
|
138
|
+
for (const scope of scopes) {
|
|
139
|
+
// Absent opt-in scopes are visible as unavailable; planning still respects opt-in.
|
|
140
|
+
add({ name: scope.name, group: scope.name, identity: canonical(scope.directory), backend: scope.backend,
|
|
141
|
+
operation: scope.operation, supported: true, exclusions: ['undeclared provider roots', 'auth and token files'],
|
|
142
|
+
roots: workspace => {
|
|
143
|
+
if (scope.name !== 'sessions')
|
|
144
|
+
return [canonical(scope.directory)];
|
|
145
|
+
let config;
|
|
146
|
+
try {
|
|
147
|
+
config = json(projectControlPath(workspace, 'aiwg.config'));
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
const manifests = [path.join(workspace, '.aiwg/sessions/discovery-manifest.json'),
|
|
153
|
+
...(config?.sessions?.discovery_manifests ?? []).map(file => path.resolve(workspace, file))];
|
|
154
|
+
return [...new Set(manifests.flatMap(file => {
|
|
155
|
+
try {
|
|
156
|
+
const manifest = json(file);
|
|
157
|
+
return Object.values(manifest?.authorizedRoots ?? {}).flat().map(canonical);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
}))];
|
|
163
|
+
}, outputPaths: scope.outputs.map(file => path.join(scope.directory, file)),
|
|
164
|
+
statePath: path.join(scope.directory, '.index', 'freshness.json') });
|
|
165
|
+
}
|
|
166
|
+
for (const name of ['models', 'datasets'])
|
|
167
|
+
add({ name, group: name,
|
|
168
|
+
identity: `${name}:${canonical(cwd)}`, backend: 'remote', operation: name === 'models'
|
|
169
|
+
? 'resolveDynamicModelCatalog' : 'dataset owner', supported: false,
|
|
170
|
+
reason: `${name} has no local deterministic freshness probe; use its owning operation`,
|
|
171
|
+
roots: () => [], outputPaths: [], exclusions: ['remote-only owners'],
|
|
172
|
+
statePath: projectAiwgPath(cwd, '.index', `${name}-freshness.json`) });
|
|
173
|
+
return adapters;
|
|
174
|
+
}
|
|
175
|
+
export function selectIndexAdapters(adapters, only = 'all') {
|
|
176
|
+
if (only === 'all')
|
|
177
|
+
return adapters;
|
|
178
|
+
const selected = adapters.filter(adapter => adapter.name === only || adapter.group === only);
|
|
179
|
+
if (!selected.length && !['sessions', 'issues', 'caches'].includes(only))
|
|
180
|
+
throw new Error(`Unknown index: ${only}`);
|
|
181
|
+
return selected;
|
|
182
|
+
}
|
|
183
|
+
async function adapterDigest(adapter, cwd) {
|
|
184
|
+
if (adapter.group === 'graphs')
|
|
185
|
+
return graphIndexDigest(adapter.graph === 'framework' ? resolveFrameworkIndexRoot(cwd) : cwd, adapter.graph);
|
|
186
|
+
if (adapter.group === 'caches')
|
|
187
|
+
return hashFiles(['metadata.json', 'dependencies.json'].map(file => path.join(getGraphIndexDir(cwd, adapter.graph), file)), adapter.identity);
|
|
188
|
+
if (adapter.group === 'issues')
|
|
189
|
+
return hashFiles(['items', 'events', 'comments'].flatMap(dir => findArtifactFiles(projectAiwgPath(cwd, 'issues', dir), ['.md', '.json', '.jsonl'])), adapter.identity);
|
|
190
|
+
if (adapter.group === 'sessions') {
|
|
191
|
+
const { readDiscoveryManifest, discoverWorkspaceHistories } = await import('../sessions/workspace-discovery.js');
|
|
192
|
+
const config = json(projectControlPath(cwd, 'aiwg.config'));
|
|
193
|
+
const manifests = [path.join(cwd, '.aiwg/sessions/discovery-manifest.json'),
|
|
194
|
+
...(config?.sessions?.discovery_manifests ?? []).map(file => path.resolve(cwd, file))];
|
|
195
|
+
const inputs = [];
|
|
196
|
+
for (const file of [...new Set(manifests)])
|
|
197
|
+
if (fs.existsSync(file)) {
|
|
198
|
+
const previous = await readDiscoveryManifest(file);
|
|
199
|
+
if (previous.workspaceId !== canonical(cwd))
|
|
200
|
+
throw new Error('discovery manifest workspace mismatch');
|
|
201
|
+
const authorizedRoots = previous.authorizedRoots ?? {};
|
|
202
|
+
if (!previous.authorizedRoots)
|
|
203
|
+
for (const source of previous.sources) {
|
|
204
|
+
const roots = authorizedRoots[source.provider] ??= [];
|
|
205
|
+
if (!roots.includes(source.authorizedRoot))
|
|
206
|
+
roots.push(source.authorizedRoot);
|
|
207
|
+
}
|
|
208
|
+
const current = await discoverWorkspaceHistories({ workspace: cwd,
|
|
209
|
+
refreshFrom: { ...previous, authorizedRoots }, knownSources: previous.sources,
|
|
210
|
+
exclude: config?.sessions?.import?.exclude });
|
|
211
|
+
inputs.push(current.manifestId);
|
|
212
|
+
}
|
|
213
|
+
if (!inputs.length)
|
|
214
|
+
throw new Error('no authorized discovery manifest');
|
|
215
|
+
return createHash('sha256').update(JSON.stringify([adapter.identity, inputs])).digest('hex');
|
|
216
|
+
}
|
|
217
|
+
// RuntimeDiscovery owns environmental discovery; its input receipt bounds this probe.
|
|
218
|
+
const runtime = json(adapter.outputPaths[0]);
|
|
219
|
+
if (!runtime?.inputFingerprint)
|
|
220
|
+
throw new Error('runtime input fingerprint unavailable');
|
|
221
|
+
return hashFiles([projectControlPath(cwd, 'aiwg.config')].filter(file => fs.existsSync(file)), runtime.inputFingerprint);
|
|
222
|
+
}
|
|
223
|
+
function outputDigest(adapter) {
|
|
224
|
+
if (!adapter.outputPaths.every(file => fs.existsSync(file)))
|
|
225
|
+
return undefined;
|
|
226
|
+
// The owner may renew stats on an unchanged build. Snapshot identity follows primary output.
|
|
227
|
+
const stat = fs.statSync(adapter.outputPaths[0], { bigint: true });
|
|
228
|
+
// Rename changes ctime during publication; inode, mtime and size survive it.
|
|
229
|
+
return createHash('sha256').update(JSON.stringify([adapter.identity, String(stat.dev), String(stat.ino),
|
|
230
|
+
String(stat.size), String(stat.mtimeNs)])).digest('hex');
|
|
231
|
+
}
|
|
232
|
+
/** Synchronous persisted view for existing reader callers; CLI/daemon also probe inputs. */
|
|
233
|
+
export function indexFreshnessSnapshot(adapter, cwd, nowMs = Date.now(), digest) {
|
|
234
|
+
const fallback = { index: adapter.name, targetStaleAfterMs: 3_600_000,
|
|
235
|
+
maxRetriesPerDay: 3, measureConvergence: true };
|
|
236
|
+
let policy = fallback;
|
|
237
|
+
try {
|
|
238
|
+
policy = resolveFreshnessPolicy(cwd, adapter);
|
|
239
|
+
if (!adapter.supported)
|
|
240
|
+
return { name: adapter.name, status: 'unsupported', policy,
|
|
241
|
+
lastVerifiedDigest: null, lastVerifiedAt: null, dirtySince: null, dirtyAgeMs: null,
|
|
242
|
+
pendingWork: 0, lastError: null, reason: adapter.reason };
|
|
243
|
+
const state = json(adapter.statePath);
|
|
244
|
+
const outputs = outputDigest(adapter);
|
|
245
|
+
const age = state?.lastVerifiedAt ? Math.max(0, nowMs - Date.parse(state.lastVerifiedAt)) : Infinity;
|
|
246
|
+
const changed = !state || !outputs || outputs !== state.outputDigest || (digest !== undefined && state.lastVerifiedDigest !== digest);
|
|
247
|
+
const dirtySince = state?.dirtySince ?? (changed ? new Date(nowMs).toISOString()
|
|
248
|
+
: age > policy.targetStaleAfterMs ? new Date(Date.parse(state.lastVerifiedAt) + policy.targetStaleAfterMs).toISOString() : null);
|
|
249
|
+
return { name: adapter.name, policy,
|
|
250
|
+
status: !outputs ? 'unavailable' : state?.lastError ? 'degraded'
|
|
251
|
+
: changed || state?.pendingWork || age > policy.targetStaleAfterMs ? 'stale' : 'fresh',
|
|
252
|
+
lastVerifiedDigest: state?.lastVerifiedDigest ?? null, lastVerifiedAt: state?.lastVerifiedAt ?? null,
|
|
253
|
+
dirtySince, dirtyAgeMs: dirtySince ? Math.max(0, nowMs - Date.parse(dirtySince)) : null,
|
|
254
|
+
pendingWork: changed ? Math.max(1, state?.pendingWork ?? 0) : state?.pendingWork ?? 0,
|
|
255
|
+
lastError: state?.lastError ?? null, convergence: policy.measureConvergence && state?.convergence
|
|
256
|
+
? { ...state.convergence, converged: state.convergence.residualJobs === 0
|
|
257
|
+
&& state.convergence.wallMs <= policy.targetStaleAfterMs } : undefined };
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
return { name: adapter.name, status: 'degraded', policy, lastVerifiedDigest: null, lastVerifiedAt: null,
|
|
261
|
+
dirtySince: null, dirtyAgeMs: null, pendingWork: 1, lastError: String(error) };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
export async function probeIndexAdapter(adapter, cwd, nowMs = Date.now()) {
|
|
265
|
+
const persisted = indexFreshnessSnapshot(adapter, cwd, nowMs);
|
|
266
|
+
if (!adapter.supported || persisted.status === 'unavailable')
|
|
267
|
+
return persisted;
|
|
268
|
+
try {
|
|
269
|
+
if (adapter.group === 'sessions') {
|
|
270
|
+
const { sessionRefreshAvailable } = await import('../sessions/refresh.js');
|
|
271
|
+
if (!sessionRefreshAvailable())
|
|
272
|
+
return { ...persisted, status: 'unavailable', pendingWork: 1,
|
|
273
|
+
reason: 'sqlite unavailable' };
|
|
274
|
+
}
|
|
275
|
+
if (adapter.group === 'caches') {
|
|
276
|
+
const { getFortemiCoreSyncStatus } = await import('./fortemi-core-sync.js');
|
|
277
|
+
const cache = getFortemiCoreSyncStatus(cwd, adapter.graph);
|
|
278
|
+
if (cache.stale && !cache.sourceIndexNewer)
|
|
279
|
+
return { ...persisted, status: 'degraded',
|
|
280
|
+
pendingWork: 1, lastError: cache.reason };
|
|
281
|
+
}
|
|
282
|
+
const digest = await adapterDigest(adapter, cwd);
|
|
283
|
+
let snapshot = indexFreshnessSnapshot(adapter, cwd, nowMs, digest);
|
|
284
|
+
// Preserve the first measured dirty observation without racing a publisher.
|
|
285
|
+
// Probe never builds or creates an absent index.
|
|
286
|
+
if (fs.existsSync(adapter.statePath)
|
|
287
|
+
&& snapshot.status === 'stale' && snapshot.lastVerifiedDigest !== digest) {
|
|
288
|
+
let release;
|
|
289
|
+
try {
|
|
290
|
+
release = await acquireDirectoryLock(adapter.group === 'graphs'
|
|
291
|
+
? `${path.dirname(adapter.statePath)}.build.lock` : path.join(getUserStateDir(), 'index-refresh.lock'), { timeoutMs: 0 });
|
|
292
|
+
const state = json(adapter.statePath);
|
|
293
|
+
if (state && !state.dirtySince && state.lastVerifiedDigest !== digest) {
|
|
294
|
+
writeIndexFile(cwd, path.basename(adapter.statePath), { ...state,
|
|
295
|
+
dirtySince: new Date(nowMs).toISOString(), pendingWork: 1 }, path.dirname(adapter.statePath));
|
|
296
|
+
}
|
|
297
|
+
snapshot = indexFreshnessSnapshot(adapter, cwd, nowMs, digest);
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
if (!(error instanceof DirectoryLockContentionError))
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
finally {
|
|
304
|
+
await release?.();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return snapshot;
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
return { ...indexFreshnessSnapshot(adapter, cwd, nowMs), status: 'degraded',
|
|
311
|
+
pendingWork: 1, lastError: error instanceof Error ? error.message : String(error) };
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
export async function indexAdapterDigest(adapter, cwd) {
|
|
315
|
+
if (!adapter.supported)
|
|
316
|
+
return undefined;
|
|
317
|
+
return adapterDigest(adapter, cwd);
|
|
318
|
+
}
|
|
319
|
+
/** Call under the existing owner lock; failure never advances verification. */
|
|
320
|
+
export function recordIndexVerification(adapter, cwd, opts, publicationDir) {
|
|
321
|
+
const now = opts.nowMs ?? Date.now();
|
|
322
|
+
let prior;
|
|
323
|
+
try {
|
|
324
|
+
prior = json(adapter.statePath);
|
|
325
|
+
}
|
|
326
|
+
catch { /* Replace corrupt receipt, never the snapshot. */ }
|
|
327
|
+
const complete = !opts.error && !!opts.before && opts.before === opts.after && !opts.pendingWork;
|
|
328
|
+
const policy = resolveFreshnessPolicy(cwd, adapter);
|
|
329
|
+
const state = { lastVerifiedDigest: complete ? opts.after : prior?.lastVerifiedDigest ?? null,
|
|
330
|
+
lastVerifiedAt: complete ? new Date(now).toISOString() : prior?.lastVerifiedAt ?? null,
|
|
331
|
+
dirtySince: complete ? null : prior?.dirtySince ?? new Date(now).toISOString(),
|
|
332
|
+
pendingWork: complete ? 0 : Math.max(1, opts.pendingWork ?? 0), lastError: opts.error ?? null,
|
|
333
|
+
outputDigest: complete ? outputDigest(publicationDir ? { ...adapter, outputPaths: adapter.outputPaths.map(file => fs.existsSync(path.join(publicationDir, path.basename(file))) ? path.join(publicationDir, path.basename(file)) : file) } : adapter) : prior?.outputDigest,
|
|
334
|
+
...(policy.measureConvergence ? { convergence: { converged: complete && opts.wallMs <= policy.targetStaleAfterMs,
|
|
335
|
+
wallMs: opts.wallMs, residualJobs: complete ? 0 : Math.max(1, opts.pendingWork ?? 0) } } : {}) };
|
|
336
|
+
writeIndexFile(cwd, path.basename(adapter.statePath), state, publicationDir ?? path.dirname(adapter.statePath));
|
|
337
|
+
return indexFreshnessSnapshot(adapter, cwd, now, opts.after);
|
|
338
|
+
}
|
|
339
|
+
//# sourceMappingURL=index-adapters.js.map
|
|
@@ -18,7 +18,7 @@ import { parseDecisionJson, parseDecisionYaml } from '../decision/entry.js';
|
|
|
18
18
|
import { validateGateDocument } from '../gates/schema.js';
|
|
19
19
|
import { INDEX_EXTRACTOR_VERSION, INDEX_VERSION, INDEX_DIR, OPERATIONAL_DISCOVERY_TYPES, PHASE_DIRECTORIES, GRAPH_CONFIGS, TEMPLATE_INDEX_EXTENSIONS, resolveGraphScanDir, loadUserGraphConfigs, loadGlobalGraphConfigs, resolveGraphBackendType, } from './types.js';
|
|
20
20
|
import { parseCitationSidecar, citationResultToEdges, buildRefToPathMap } from './citation-parser.js';
|
|
21
|
-
import { writeIndexFile, resolveIndexDir, loadGraphIndexFile } from './index-reader.js';
|
|
21
|
+
import { writeIndexFile, resolveIndexDir, loadGraphIndexFile, publishIndexFiles } from './index-reader.js';
|
|
22
22
|
import { loadManifest, writeManifest, statMatches, makeEntry } from './checksum-manifest.js';
|
|
23
23
|
import { FRAMEWORK_RELATIONS_REVISION } from './framework-source-digest.js';
|
|
24
24
|
import { normalizeOperationalState } from './operational-state.js';
|
|
@@ -26,6 +26,9 @@ import { DEFAULT_PROJECT_AIWG_DIR, resolveProjectAiwgDir, } from '../config/proj
|
|
|
26
26
|
import { normalizeStateTransferProjection } from './state-transfer.js';
|
|
27
27
|
import { resolveIndexWorkers, runIndexPreparation } from './index-preparation.js';
|
|
28
28
|
import { collectGraphIndexFiles, findArtifactFiles, indexPathFor } from './index-files.js';
|
|
29
|
+
import { acquireDirectoryLock } from './prebuilt-build-lock.js';
|
|
30
|
+
import { graphIndexDigest, indexAdapterRegistry, recordIndexVerification } from './index-adapters.js';
|
|
31
|
+
import { loadMemoryRecord } from '../memory/maintenance-record.js';
|
|
29
32
|
function absoluteEntryPath(cwd, entryPath, graph) {
|
|
30
33
|
if ((!graph || graph === 'project') && entryPath.startsWith(`${DEFAULT_PROJECT_AIWG_DIR}/`)) {
|
|
31
34
|
return path.join(resolveProjectAiwgDir(cwd), entryPath.slice(DEFAULT_PROJECT_AIWG_DIR.length + 1));
|
|
@@ -369,6 +372,9 @@ function inferType(data, filePath) {
|
|
|
369
372
|
// Rules/RULES-INDEX.md is a curated index file, not a rule.
|
|
370
373
|
if (basename === 'rules-index' || basename === 'index')
|
|
371
374
|
break;
|
|
375
|
+
// <rule>.core.md is a delivery body (#3450), not a separate rule.
|
|
376
|
+
if (/\.core\.md$/i.test(filePath))
|
|
377
|
+
break;
|
|
372
378
|
if (isMarkdown)
|
|
373
379
|
return 'rule';
|
|
374
380
|
break;
|
|
@@ -836,27 +842,73 @@ function resolveInstallRootHint() {
|
|
|
836
842
|
catch { /* fall through */ }
|
|
837
843
|
return '<aiwg install root>';
|
|
838
844
|
}
|
|
845
|
+
/** Current source identities for rule bindings; no search index or graph is written. */
|
|
846
|
+
export async function collectRuleBindingEntries(cwd, scope) {
|
|
847
|
+
const fileStats = new Map();
|
|
848
|
+
const files = await collectGraphIndexFiles(cwd, undefined, { scope, fileStats });
|
|
849
|
+
return files.flatMap(fullPath => {
|
|
850
|
+
const stat = fileStats.get(fullPath) ?? fs.statSync(fullPath);
|
|
851
|
+
const entry = extractIndexArtifact({ fullPath, relativePath: indexPathFor(cwd, fullPath),
|
|
852
|
+
mtimeMs: stat.mtimeMs, birthtimeMs: stat.birthtimeMs, sizeBytes: stat.size }, true).entry;
|
|
853
|
+
return entry && ['skill', 'agent', 'flow'].includes(entry.type) ? [entry] : [];
|
|
854
|
+
});
|
|
855
|
+
}
|
|
839
856
|
/** Shared serial/worker read, checksum and extraction. No index output or global reduction. */
|
|
840
857
|
export function prepareIndexArtifact(job) {
|
|
858
|
+
const warnings = [];
|
|
859
|
+
const result = extractIndexArtifact(job, false, warnings);
|
|
860
|
+
return { ...result, ...(warnings.length ? { warnings } : {}) };
|
|
861
|
+
}
|
|
862
|
+
function extractIndexArtifact(job, bindingOnly, warnings = []) {
|
|
841
863
|
const { fullPath, relativePath } = job;
|
|
842
864
|
const content = fs.readFileSync(fullPath, 'utf8');
|
|
843
865
|
const checksum = computeChecksum(content);
|
|
844
866
|
if (job.existingChecksum === checksum)
|
|
845
867
|
return { checksum };
|
|
846
868
|
const { data, body } = parseFrontmatter(content);
|
|
869
|
+
let memoryRecord;
|
|
870
|
+
if (data.memoryRecord !== undefined) {
|
|
871
|
+
try {
|
|
872
|
+
memoryRecord = loadMemoryRecord(data.memoryRecord);
|
|
873
|
+
}
|
|
874
|
+
catch (error) {
|
|
875
|
+
warnings.push(`${relativePath}: invalid memoryRecord metadata quarantined: ${error instanceof Error ? error.message : String(error)}`);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
const inferredType = inferType(data, relativePath);
|
|
879
|
+
const bindingType = ['skill', 'agent', 'flow'].includes(inferredType);
|
|
880
|
+
// Only YAML flows can promote an otherwise unrelated document to a binding target.
|
|
881
|
+
if (bindingOnly && !bindingType && !/\.ya?ml$/i.test(relativePath))
|
|
882
|
+
return { checksum };
|
|
847
883
|
// YAML Flow documents (workflow.aiwg.io/v1) are pure YAML, not
|
|
848
884
|
// markdown+frontmatter — detect them up front so they classify and
|
|
849
885
|
// become discoverable (#1540).
|
|
850
886
|
const flow = parseFlowDoc(content, relativePath);
|
|
887
|
+
if (bindingOnly && !flow && !bindingType)
|
|
888
|
+
return { checksum };
|
|
851
889
|
const decision = flow ? null : parseDecisionDoc(content, relativePath);
|
|
852
890
|
const gatePack = flow || decision ? null : parseGatePackDoc(content, relativePath);
|
|
853
|
-
|
|
891
|
+
if (bindingOnly && (decision || gatePack))
|
|
892
|
+
return { checksum };
|
|
854
893
|
const physicalType = inferType({ ...data, type: undefined }, relativePath);
|
|
855
894
|
const runbook = flow || decision || gatePack ? null : parseRunbookDoc(data, body, relativePath);
|
|
856
895
|
const schemaDoc = inferredType === 'schema' ? parseSchemaDoc(content, relativePath) : null;
|
|
857
896
|
const title = flow?.name ?? decision?.name ?? gatePack?.name ?? schemaDoc?.title ?? extractTitle(data, body);
|
|
858
897
|
const phase = typeof data.phase === 'string' ? data.phase : inferPhase(relativePath);
|
|
859
898
|
const type = flow?.type ?? decision?.type ?? gatePack?.type ?? (runbook ? 'runbook' : inferredType);
|
|
899
|
+
// Binding resolution consumes only identity and title. Keep the same canonical
|
|
900
|
+
// classification (including declarative flows and runbooks), without extracting
|
|
901
|
+
// search terms, mentions, links, or constructing a persistent graph.
|
|
902
|
+
if (bindingOnly)
|
|
903
|
+
return { checksum, entry: {
|
|
904
|
+
path: relativePath, type, title, phase,
|
|
905
|
+
...(memoryRecord ? { memoryRecord } : {}),
|
|
906
|
+
name: flow?.name ?? decision?.name ?? gatePack?.name ?? schemaDoc?.name
|
|
907
|
+
?? (OPERATIONAL_DISCOVERY_TYPES.includes(type)
|
|
908
|
+
? extractCanonicalName(data, relativePath) : undefined),
|
|
909
|
+
tags: [], dependencies: [], dependents: [], checksum,
|
|
910
|
+
created: '', updated: '', summary: '',
|
|
911
|
+
} };
|
|
860
912
|
const tags = flow?.tags ?? decision?.tags ?? gatePack?.tags ?? (Array.isArray(data.tags) ? data.tags.map(String) : []);
|
|
861
913
|
const summary = flow?.description ?? decision?.description ?? gatePack?.description ?? schemaDoc?.capability ?? runbook?.capability ?? extractSummary(data, body);
|
|
862
914
|
const dependencies = extractMentions(content);
|
|
@@ -882,6 +934,8 @@ export function prepareIndexArtifact(job) {
|
|
|
882
934
|
// remain searchable even when the rendered title strips the hyphen.
|
|
883
935
|
const name = flow?.name ?? decision?.name ?? gatePack?.name ?? schemaDoc?.name ?? (isDiscoverable ? extractCanonicalName(data, relativePath) : undefined);
|
|
884
936
|
const entry = {
|
|
937
|
+
...(memoryRecord ? { memoryRecord } : {}),
|
|
938
|
+
...(typeof data.memoryCategory === 'string' ? { memoryCategory: data.memoryCategory } : {}),
|
|
885
939
|
path: relativePath,
|
|
886
940
|
type,
|
|
887
941
|
...(kind ? { kind } : {}),
|
|
@@ -910,6 +964,67 @@ export function prepareIndexArtifact(job) {
|
|
|
910
964
|
return { checksum, entry };
|
|
911
965
|
}
|
|
912
966
|
export async function buildIndex(cwd, options = {}) {
|
|
967
|
+
loadUserGraphConfigs(cwd);
|
|
968
|
+
loadGlobalGraphConfigs();
|
|
969
|
+
const scanDirs = options.scope ? [path.join(cwd, options.scope)] : options.graph
|
|
970
|
+
? GRAPH_CONFIGS[options.graph]?.scanDirs.map(dir => resolveGraphScanDir(cwd, dir)) ?? []
|
|
971
|
+
: [resolveProjectAiwgDir(cwd)];
|
|
972
|
+
if (!scanDirs.some(dir => fs.existsSync(dir))) {
|
|
973
|
+
// Keep the owner's missing-root diagnostic/exit path before creating output directories.
|
|
974
|
+
await buildIndexSnapshot(cwd, options, () => '');
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
const directory = options.outputDir
|
|
978
|
+
? path.join(options.outputDir, options.graph ? `.aiwg/.index/${options.graph}` : INDEX_DIR)
|
|
979
|
+
: resolveIndexDir(cwd, options.graph);
|
|
980
|
+
// Manual and background builds acquire the same per-output owner lock.
|
|
981
|
+
const release = await acquireDirectoryLock(`${directory}.build.lock`, {
|
|
982
|
+
timeoutMs: options.lockWaitMs ?? 180_000, reclaimDeadOwner: true,
|
|
983
|
+
});
|
|
984
|
+
let staging;
|
|
985
|
+
const start = Date.now();
|
|
986
|
+
let owned;
|
|
987
|
+
try {
|
|
988
|
+
const adapter = !options.scope && !options.includeIgnored
|
|
989
|
+
? indexAdapterRegistry(cwd).find(item => item.name === (options.graph ?? 'project')) : undefined;
|
|
990
|
+
owned = adapter && directory === path.dirname(adapter.statePath) ? adapter : undefined;
|
|
991
|
+
const observation = { selection: {} };
|
|
992
|
+
const preparePublication = () => {
|
|
993
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
994
|
+
return staging ??= fs.mkdtempSync(path.join(directory, '.build-'));
|
|
995
|
+
};
|
|
996
|
+
await buildIndexSnapshot(cwd, options, preparePublication, owned ? observation : undefined);
|
|
997
|
+
// A receipt hit verifies the existing snapshot directly against the scan's
|
|
998
|
+
// stats. There is no extraction interval to recheck. Real builds still probe
|
|
999
|
+
// again after preparation so concurrent source changes remain pending.
|
|
1000
|
+
const after = owned ? observation.unchanged ? observation.before
|
|
1001
|
+
: await graphIndexDigest(cwd, options.graph, { selection: observation.selection }) : undefined;
|
|
1002
|
+
// All extraction and backend work has succeeded before any live file moves.
|
|
1003
|
+
if (owned)
|
|
1004
|
+
recordIndexVerification(owned, cwd, { before: observation.before, after, wallMs: Date.now() - start }, staging);
|
|
1005
|
+
if (staging)
|
|
1006
|
+
publishIndexFiles(staging, directory);
|
|
1007
|
+
}
|
|
1008
|
+
catch (error) {
|
|
1009
|
+
if (owned) {
|
|
1010
|
+
try {
|
|
1011
|
+
recordIndexVerification(owned, cwd, { error: error instanceof Error ? error.message : String(error), wallMs: Date.now() - start });
|
|
1012
|
+
}
|
|
1013
|
+
catch { /* Preserve the original build failure if recording is unavailable. */ }
|
|
1014
|
+
}
|
|
1015
|
+
throw error;
|
|
1016
|
+
}
|
|
1017
|
+
finally {
|
|
1018
|
+
try {
|
|
1019
|
+
if (staging)
|
|
1020
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
1021
|
+
}
|
|
1022
|
+
finally {
|
|
1023
|
+
await release();
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
async function buildIndexSnapshot(cwd, options, preparePublication, observation) {
|
|
913
1028
|
const { force = false, verbose = false, scope, includeIgnored = false, outputDir, graph, explicit = true } = options;
|
|
914
1029
|
const workers = resolveIndexWorkers(options.workers);
|
|
915
1030
|
const startTime = Date.now();
|
|
@@ -979,10 +1094,12 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
979
1094
|
// effectiveOutputCwd is used for backward-compat loadMetadataIndex calls
|
|
980
1095
|
const effectiveOutputCwd = outputDir ?? cwd;
|
|
981
1096
|
if (graph === 'source') {
|
|
1097
|
+
if (observation)
|
|
1098
|
+
observation.before = await graphIndexDigest(cwd, graph, { selection: observation.selection });
|
|
982
1099
|
const { buildSourceGraphIndex } = await import('./source-graph.js');
|
|
983
1100
|
await buildSourceGraphIndex({
|
|
984
1101
|
cwd,
|
|
985
|
-
outputDir:
|
|
1102
|
+
outputDir: preparePublication(),
|
|
986
1103
|
effectiveOutputCwd,
|
|
987
1104
|
verbose,
|
|
988
1105
|
});
|
|
@@ -1001,7 +1118,7 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
1001
1118
|
: loadManifest(indexOutputDir);
|
|
1002
1119
|
const useFilenameMetadata = graphConfig?.nodeStrategy === 'filename-metadata';
|
|
1003
1120
|
const reusable = !useFilenameMetadata && !graphConfig?.metadataSupplements?.length && !graphConfig?.edgeExtraction;
|
|
1004
|
-
const fileStats = !force && reusable ? new Map() : undefined;
|
|
1121
|
+
const fileStats = observation || (!force && reusable) ? new Map() : undefined;
|
|
1005
1122
|
const nextManifestEntries = {};
|
|
1006
1123
|
const manifestStats = {
|
|
1007
1124
|
checked: 0,
|
|
@@ -1011,7 +1128,9 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
1011
1128
|
pruned: 0,
|
|
1012
1129
|
};
|
|
1013
1130
|
// Collect files from all scan directories
|
|
1014
|
-
const files = await collectGraphIndexFiles(cwd, graph, { scope, includeIgnored, fileStats });
|
|
1131
|
+
const files = await collectGraphIndexFiles(cwd, graph, { scope, includeIgnored, fileStats, selection: observation?.selection });
|
|
1132
|
+
if (observation)
|
|
1133
|
+
observation.before = await graphIndexDigest(cwd, graph, { files, fileStats });
|
|
1015
1134
|
const entries = {};
|
|
1016
1135
|
const tagIndex = {};
|
|
1017
1136
|
const depGraph = {};
|
|
@@ -1052,7 +1171,7 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
1052
1171
|
};
|
|
1053
1172
|
const entriesDigest = (values) => createHash('sha256').update(JSON.stringify(values)).digest('hex');
|
|
1054
1173
|
const writeBuildManifest = (values) => {
|
|
1055
|
-
writeManifest(
|
|
1174
|
+
writeManifest(publicationDir, { version: 1, generated: new Date().toISOString(), entries: values,
|
|
1056
1175
|
...(reusable ? { receipt: {
|
|
1057
1176
|
indexVersion: INDEX_VERSION, extractorVersion: INDEX_EXTRACTOR_VERSION, buildConfigHash,
|
|
1058
1177
|
sourceRoot: path.resolve(cwd), metadataSignature: fileSignature('metadata.json'), entriesDigest: entriesDigest(values),
|
|
@@ -1086,11 +1205,16 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
1086
1205
|
manifestStats.checked = files.length;
|
|
1087
1206
|
manifestStats.statSkipped = files.length;
|
|
1088
1207
|
const buildTimeMs = Date.now() - startTime;
|
|
1208
|
+
// Only stats changes on a receipt hit; its existing atomic writer is
|
|
1209
|
+
// sufficient under the owner lock, without staging or backup copies.
|
|
1089
1210
|
writeIndexFile(effectiveOutputCwd, 'stats.json', { ...stats, builtAt: new Date().toISOString(), buildTimeMs }, indexOutputDir);
|
|
1211
|
+
if (observation)
|
|
1212
|
+
observation.unchanged = true;
|
|
1090
1213
|
reportBuild(buildTimeMs, files.length);
|
|
1091
1214
|
return;
|
|
1092
1215
|
}
|
|
1093
1216
|
}
|
|
1217
|
+
const publicationDir = preparePublication();
|
|
1094
1218
|
const existingIndex = force ? null : outputDir
|
|
1095
1219
|
? loadIndexFromDir(indexOutputDir, 'metadata.json')
|
|
1096
1220
|
: loadGraphIndexFile(effectiveOutputCwd, 'metadata.json', graph);
|
|
@@ -1121,6 +1245,10 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
1121
1245
|
...(!force && existingEntries[relativePath] ? { existingChecksum: existingEntries[relativePath].checksum } : {}) });
|
|
1122
1246
|
}
|
|
1123
1247
|
const preparation = await runIndexPreparation(jobs, prepareIndexArtifact, import.meta.url, workers, options.onDiagnostic ?? (message => console.warn(`Index preparation: ${message}`)));
|
|
1248
|
+
for (const result of preparation.results)
|
|
1249
|
+
for (const warning of result.warnings ?? []) {
|
|
1250
|
+
(options.onDiagnostic ?? (message => console.warn(`Index preparation: ${message}`)))(warning);
|
|
1251
|
+
}
|
|
1124
1252
|
const preparedArtifacts = new Map(jobs.map((job, index) => [job.fullPath, preparation.results[index]]));
|
|
1125
1253
|
for (const fullPath of files) {
|
|
1126
1254
|
const relativePath = indexPathFor(cwd, fullPath, graph);
|
|
@@ -1221,14 +1349,15 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
1221
1349
|
const projectionNeedsRepair = graph === 'framework' && !relationsReceiptMatches();
|
|
1222
1350
|
if (graph === 'framework') {
|
|
1223
1351
|
const { writeRelations } = await import('./relations-builder.js');
|
|
1224
|
-
|
|
1352
|
+
if (force || !relationsReceiptMatches())
|
|
1353
|
+
await writeRelations(entries, effectiveOutputCwd, publicationDir, true);
|
|
1225
1354
|
}
|
|
1226
1355
|
const buildTimeMs = Date.now() - startTime;
|
|
1227
1356
|
writeIndexFile(effectiveOutputCwd, 'stats.json', {
|
|
1228
1357
|
...stats,
|
|
1229
1358
|
builtAt: new Date().toISOString(),
|
|
1230
1359
|
buildTimeMs,
|
|
1231
|
-
},
|
|
1360
|
+
}, publicationDir);
|
|
1232
1361
|
// Preserve no-op manifest bytes after output-only timestamp drift.
|
|
1233
1362
|
// Renew receipts only for changed source stats or repaired projections.
|
|
1234
1363
|
if (manifestStats.checksumSkipped > 0 || projectionNeedsRepair)
|
|
@@ -1354,7 +1483,7 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
1354
1483
|
}
|
|
1355
1484
|
if (graph === 'framework') {
|
|
1356
1485
|
const { writeRelations } = await import('./relations-builder.js');
|
|
1357
|
-
await writeRelations(entries, effectiveOutputCwd,
|
|
1486
|
+
await writeRelations(entries, effectiveOutputCwd, publicationDir, force || !relationsReceiptMatches(), indexOutputDir);
|
|
1358
1487
|
}
|
|
1359
1488
|
const buildTimeMs = Date.now() - startTime;
|
|
1360
1489
|
// Write index files
|
|
@@ -1366,13 +1495,13 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
1366
1495
|
buildTimeMs,
|
|
1367
1496
|
entries,
|
|
1368
1497
|
};
|
|
1369
|
-
writeIndexFile(effectiveOutputCwd, 'metadata.json', index,
|
|
1370
|
-
writeIndexFile(effectiveOutputCwd, 'tags.json', tagIndex,
|
|
1371
|
-
writeIndexFile(effectiveOutputCwd, 'dependencies.json', depGraph,
|
|
1498
|
+
writeIndexFile(effectiveOutputCwd, 'metadata.json', index, publicationDir);
|
|
1499
|
+
writeIndexFile(effectiveOutputCwd, 'tags.json', tagIndex, publicationDir);
|
|
1500
|
+
writeIndexFile(effectiveOutputCwd, 'dependencies.json', depGraph, publicationDir);
|
|
1372
1501
|
// Materialize the configured backend and always retain dependencies.json as
|
|
1373
1502
|
// the stable compatibility/export contract.
|
|
1374
1503
|
const { createGraphBackend } = await import('./graph-backend.js');
|
|
1375
|
-
const persistentPath = backendType === 'sqlite' ? path.join(
|
|
1504
|
+
const persistentPath = backendType === 'sqlite' ? path.join(publicationDir, 'graph.db') : undefined;
|
|
1376
1505
|
let selectedBackend;
|
|
1377
1506
|
try {
|
|
1378
1507
|
selectedBackend = await createGraphBackend(backendType, persistentPath);
|
|
@@ -1430,7 +1559,7 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
1430
1559
|
orphanedArtifacts: orphaned,
|
|
1431
1560
|
mostReferenced,
|
|
1432
1561
|
},
|
|
1433
|
-
},
|
|
1562
|
+
}, publicationDir);
|
|
1434
1563
|
writeBuildManifest(nextManifestEntries);
|
|
1435
1564
|
// Report
|
|
1436
1565
|
reportBuild(buildTimeMs, total);
|