@aiwg/cli 2026.10.5 → 2026.10.6
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 +3 -1
- 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/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-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/cli/handlers/decision.js +19 -1
- 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/local-executor.js +5 -3
- 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 +428 -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 +279 -93
- package/dist/src/cli/handlers/workspace.js +3 -1
- package/dist/src/cli/prompt-utils.js +8 -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/native-sandbox-warnings.js +25 -0
- package/dist/src/contributors/validation.js +6 -5
- package/dist/src/decision/admission.js +7 -3
- package/dist/src/decision/benchmark.js +469 -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 +18 -15
- package/dist/src/decision/qualification/load-staged.js +539 -0
- package/dist/src/decision/qualification/load.js +109 -33
- package/dist/src/decision/qualification/runner.js +5 -2
- 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 +233 -0
- package/dist/src/discovery/plan-apply.js +571 -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/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-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/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/mcp/cli.mjs +5 -6
- package/dist/src/mcp/config-file.mjs +6 -2
- package/dist/src/mcp/config-layers.mjs +23 -7
- 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 +17 -4
- 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/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/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 +7 -0
- package/dist/src/models/worker-installation.mjs +169 -37
- package/dist/src/models/wrapper-route.js +1 -1
- package/dist/src/output-modes/registry.js +8 -7
- package/dist/src/plugin/metadata-validator.js +1 -1
- 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 +114 -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 +42 -6
- package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
- package/dist/src/smiths/context-pipeline/workspace-context.js +106 -10
- package/dist/src/update/checker.d.mts +4 -0
- package/dist/src/update/checker.mjs +14 -0
- 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 +19 -13
- package/tools/agents/providers/claude.mjs +1 -1
- package/tools/agents/providers/codex.mjs +50 -56
- package/tools/agents/providers/copilot.mjs +8 -2
- 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 +7 -13
- package/tools/agents/providers/muse-hooks.mjs +21 -14
- package/tools/agents/providers/muse.mjs +7 -2
- package/tools/agents/providers/omp.mjs +28 -4
- 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 +263 -71
- package/tools/agents/providers/windsurf.mjs +7 -0
- 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/muse-adapter.mjs +12 -2
- 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 +7 -4
- package/tools/ralph-external/session-launcher.mjs +280 -32
- package/tools/skills/deploy-skills-codex.mjs +11 -7
- package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
|
@@ -5,9 +5,9 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import { parse as parseYaml } from "yaml";
|
|
6
6
|
import { GRAPH_CONFIGS, getProjectIndexRoot, loadGlobalGraphConfigs } from "./types.js";
|
|
7
7
|
import { buildAiwgFortemiIndexExport, } from "./browser-export.js";
|
|
8
|
-
import { loadGraphIndexFile } from "./index-reader.js";
|
|
8
|
+
import { loadGraphIndexFile, publishIndexFiles } from "./index-reader.js";
|
|
9
9
|
import { getFrameworkIndexBaseDir } from './framework-index-paths.js';
|
|
10
|
-
function
|
|
10
|
+
export function getFortemiCoreIndexDir(cwd, graph) {
|
|
11
11
|
loadGlobalGraphConfigs();
|
|
12
12
|
if (graph === 'framework')
|
|
13
13
|
return path.join(getFrameworkIndexBaseDir(cwd), 'fortemi-core', graph);
|
|
@@ -180,14 +180,15 @@ function writeExport(file, exported, maxChars) {
|
|
|
180
180
|
}
|
|
181
181
|
export function syncFortemiCoreIndex(cwd, options = {}) {
|
|
182
182
|
const graph = options.graph ?? "project";
|
|
183
|
-
const dir =
|
|
183
|
+
const dir = getFortemiCoreIndexDir(cwd, graph);
|
|
184
184
|
const exportPath = path.join(dir, "aiwg-fortemi-index-v2.json");
|
|
185
185
|
const manifestPath = path.join(dir, "manifest.json");
|
|
186
186
|
const maxChars = options.maxChunkChars ?? 64 * 1024;
|
|
187
187
|
if (!Number.isSafeInteger(maxChars) || maxChars < 64)
|
|
188
188
|
throw new Error("maxChunkChars must be an integer of at least 64");
|
|
189
189
|
fs.mkdirSync(dir, { recursive: true });
|
|
190
|
-
const
|
|
190
|
+
const staging = fs.mkdtempSync(path.join(dir, ".sync-"));
|
|
191
|
+
const temporaryPath = path.join(staging, `.export-${randomUUID()}.tmp`);
|
|
191
192
|
const previous = readManifest(manifestPath);
|
|
192
193
|
const sourceBuiltAt = sourceIndexBuiltAt(cwd, graph);
|
|
193
194
|
const buildExport = (generatedAt) => {
|
|
@@ -200,10 +201,25 @@ export function syncFortemiCoreIndex(cwd, options = {}) {
|
|
|
200
201
|
});
|
|
201
202
|
const checksum = writeExport(temporaryPath, exported, maxChars);
|
|
202
203
|
const recordsWithConcepts = exported.items.filter((item) => (item.skos_concepts?.length ?? 0) > 0).length;
|
|
204
|
+
const shortTextThreshold = 200;
|
|
205
|
+
let shortTextCount = 0;
|
|
206
|
+
let filenameTitleCount = 0;
|
|
207
|
+
for (const item of exported.items) {
|
|
208
|
+
if (item.text.trim().length < shortTextThreshold)
|
|
209
|
+
shortTextCount++;
|
|
210
|
+
if (item.title.trim() === path.posix.basename(item.source.path))
|
|
211
|
+
filenameTitleCount++;
|
|
212
|
+
}
|
|
203
213
|
return {
|
|
204
214
|
generatedAt,
|
|
205
215
|
checksum,
|
|
206
216
|
itemCount: exported.items.length,
|
|
217
|
+
textQuality: {
|
|
218
|
+
short_text_threshold_chars: shortTextThreshold,
|
|
219
|
+
short_text_count: shortTextCount,
|
|
220
|
+
filename_title_count: filenameTitleCount,
|
|
221
|
+
degenerate: exported.items.length === 0 || shortTextCount / exported.items.length >= 0.8,
|
|
222
|
+
},
|
|
207
223
|
skosCoverage: {
|
|
208
224
|
records_with_concepts: recordsWithConcepts,
|
|
209
225
|
total_records: exported.items.length,
|
|
@@ -228,7 +244,7 @@ export function syncFortemiCoreIndex(cwd, options = {}) {
|
|
|
228
244
|
}
|
|
229
245
|
}
|
|
230
246
|
}
|
|
231
|
-
fs.renameSync(temporaryPath, exportPath);
|
|
247
|
+
fs.renameSync(temporaryPath, path.join(staging, path.basename(exportPath)));
|
|
232
248
|
const manifest = {
|
|
233
249
|
schema_version: "aiwg.fortemi.core-sync.v1",
|
|
234
250
|
backend: "fortemi-core",
|
|
@@ -238,19 +254,21 @@ export function syncFortemiCoreIndex(cwd, options = {}) {
|
|
|
238
254
|
export_schema_version: "aiwg.fortemi.index.export.v2",
|
|
239
255
|
export_checksum: built.checksum,
|
|
240
256
|
item_count: built.itemCount,
|
|
257
|
+
text_quality: built.textQuality,
|
|
241
258
|
skos_coverage: built.skosCoverage,
|
|
242
259
|
status,
|
|
243
260
|
source_index_built_at: sourceBuiltAt,
|
|
244
261
|
};
|
|
245
|
-
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
262
|
+
fs.writeFileSync(path.join(staging, path.basename(manifestPath)), JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
263
|
+
publishIndexFiles(staging, dir);
|
|
246
264
|
return manifest;
|
|
247
265
|
}
|
|
248
266
|
finally {
|
|
249
|
-
fs.rmSync(
|
|
267
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
250
268
|
}
|
|
251
269
|
}
|
|
252
270
|
export function getFortemiCoreSyncStatus(cwd, graph = "project") {
|
|
253
|
-
const dir =
|
|
271
|
+
const dir = getFortemiCoreIndexDir(cwd, graph);
|
|
254
272
|
const manifestPath = path.join(dir, "manifest.json");
|
|
255
273
|
const exportPath = path.join(dir, "aiwg-fortemi-index-v2.json");
|
|
256
274
|
const { manifest, reason: manifestReadReason } = readManifestResult(manifestPath);
|
|
@@ -9,8 +9,17 @@
|
|
|
9
9
|
* @tests @test/unit/artifacts/graph-query.test.ts
|
|
10
10
|
*/
|
|
11
11
|
import { normalizeEdges } from './types.js';
|
|
12
|
-
import { buildFortemiCoreDependencyGraph } from './fortemi-core-query-adapter.js';
|
|
12
|
+
import { buildFortemiCoreDependencyGraph, loadFortemiCoreMetadataEntries } from './fortemi-core-query-adapter.js';
|
|
13
13
|
import { closeGraphBackend, openGraphBackend } from './backend-runtime.js';
|
|
14
|
+
import { loadGraphIndexFile } from './index-reader.js';
|
|
15
|
+
import { isOrdinaryMemoryEntry } from './query-engine.js';
|
|
16
|
+
function graphMemoryMetadata(cwd, graph, backend) {
|
|
17
|
+
const local = loadGraphIndexFile(cwd, 'metadata.json', graph)?.entries ?? {};
|
|
18
|
+
if (backend === 'fortemi-core') {
|
|
19
|
+
return { ...Object.fromEntries(loadFortemiCoreMetadataEntries(cwd, graph).entries.map(entry => [entry.path, entry])), ...local };
|
|
20
|
+
}
|
|
21
|
+
return local;
|
|
22
|
+
}
|
|
14
23
|
/**
|
|
15
24
|
* Get neighbors of a node in a dependency graph.
|
|
16
25
|
*
|
|
@@ -20,15 +29,15 @@ import { closeGraphBackend, openGraphBackend } from './backend-runtime.js';
|
|
|
20
29
|
* @param edgeType - Optional filter by edge type
|
|
21
30
|
* @returns Array of neighbor paths
|
|
22
31
|
*/
|
|
23
|
-
export function getNeighbors(graph, node, direction = 'both', edgeType) {
|
|
32
|
+
export function getNeighbors(graph, node, direction = 'both', edgeType, metadata = {}) {
|
|
24
33
|
const entry = graph[node];
|
|
25
|
-
if (!entry)
|
|
34
|
+
if (!entry || (metadata[node] && !isOrdinaryMemoryEntry(metadata[node])))
|
|
26
35
|
return [];
|
|
27
36
|
const results = new Set();
|
|
28
37
|
if (direction === 'in' || direction === 'both') {
|
|
29
38
|
const edges = normalizeEdges(entry.upstream);
|
|
30
39
|
for (const edge of edges) {
|
|
31
|
-
if (!edgeType || edge.type === edgeType) {
|
|
40
|
+
if ((!edgeType || edge.type === edgeType) && (!metadata[edge.path] || isOrdinaryMemoryEntry(metadata[edge.path]))) {
|
|
32
41
|
results.add(edge.path);
|
|
33
42
|
}
|
|
34
43
|
}
|
|
@@ -36,7 +45,7 @@ export function getNeighbors(graph, node, direction = 'both', edgeType) {
|
|
|
36
45
|
if (direction === 'out' || direction === 'both') {
|
|
37
46
|
const edges = normalizeEdges(entry.downstream);
|
|
38
47
|
for (const edge of edges) {
|
|
39
|
-
if (!edgeType || edge.type === edgeType) {
|
|
48
|
+
if ((!edgeType || edge.type === edgeType) && (!metadata[edge.path] || isOrdinaryMemoryEntry(metadata[edge.path]))) {
|
|
40
49
|
results.add(edge.path);
|
|
41
50
|
}
|
|
42
51
|
}
|
|
@@ -134,7 +143,7 @@ export async function showNeighbors(cwd, options) {
|
|
|
134
143
|
}
|
|
135
144
|
process.exit(1);
|
|
136
145
|
}
|
|
137
|
-
const neighbors = getNeighbors(graph, resolved, direction, edgeType);
|
|
146
|
+
const neighbors = getNeighbors(graph, resolved, direction, edgeType, graphMemoryMetadata(cwd, graphType, backend));
|
|
138
147
|
if (json) {
|
|
139
148
|
console.log(JSON.stringify({
|
|
140
149
|
graph: graphType,
|
|
@@ -208,8 +217,9 @@ export async function executeSetQuery(cwd, options) {
|
|
|
208
217
|
console.error(`Error: Node '${nodeB}' not found in graph '${graphType}'.`);
|
|
209
218
|
process.exit(1);
|
|
210
219
|
}
|
|
211
|
-
const
|
|
212
|
-
const
|
|
220
|
+
const metadata = graphMemoryMetadata(cwd, graphType, backend);
|
|
221
|
+
const neighborsA = getNeighbors(graph, resolvedA, direction, edgeType, metadata);
|
|
222
|
+
const neighborsB = getNeighbors(graph, resolvedB, direction, edgeType, metadata);
|
|
213
223
|
let result;
|
|
214
224
|
switch (op) {
|
|
215
225
|
case 'intersection':
|
|
@@ -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
|