@hassangameryt/freeflow 0.4.0 → 0.6.0
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -1
- package/.codex-plugin/plugin.json +2 -1
- package/README.md +150 -155
- package/capabilities/cognitive-routing/SKILL.md +229 -0
- package/capabilities/context-virtualization/SKILL.md +103 -0
- package/capabilities/conversation-history/SKILL.md +53 -0
- package/command-surface.json +17 -13
- package/hooks/freeflow-runtime-context.mjs +393 -159
- package/hooks/hooks.json +26 -1
- package/hooks/tests/check-runtime-context-hook.sh +223 -54
- package/package.json +25 -16
- package/pi-extension/dist/cognitive-routing/commands.js +96 -0
- package/pi-extension/dist/cognitive-routing/config.js +236 -0
- package/pi-extension/dist/cognitive-routing/controller.js +862 -0
- package/pi-extension/dist/cognitive-routing/history.js +439 -0
- package/pi-extension/dist/cognitive-routing/host.js +126 -0
- package/pi-extension/dist/cognitive-routing/runtime.js +43 -0
- package/pi-extension/dist/cognitive-routing/tool.js +211 -0
- package/pi-extension/dist/cognitive-routing/types.js +6 -0
- package/pi-extension/dist/context-virtualization/commands.js +89 -0
- package/pi-extension/dist/context-virtualization/projector.js +42 -0
- package/pi-extension/dist/context-virtualization/runtime.js +356 -0
- package/pi-extension/dist/context-virtualization/state.js +57 -0
- package/pi-extension/dist/context-virtualization/types.js +10 -0
- package/pi-extension/dist/conversation-history/passages.js +66 -0
- package/pi-extension/dist/conversation-history/ranking.js +105 -0
- package/pi-extension/dist/conversation-history/runtime.js +226 -0
- package/pi-extension/dist/conversation-history/search.js +56 -0
- package/pi-extension/dist/conversation-history/types.js +1 -0
- package/pi-extension/dist/freeflow-context/resolver.js +120 -0
- package/pi-extension/dist/freeflow-context/runtime.js +80 -0
- package/pi-extension/dist/freeflow-context/source-projector.js +95 -0
- package/pi-extension/dist/freeflow-context/tool.js +308 -0
- package/pi-extension/dist/freeflow-context/types.js +12 -0
- package/pi-extension/dist/freeflow-context/visibility.js +28 -0
- package/pi-extension/dist/index.js +516 -98
- package/pi-extension/dist/runtime/runtime-context.js +318 -271
- package/pi-extension/dist/runtime/runtime-identity.js +5 -0
- package/pi-extension/dist/settings/settings-tui.js +78 -0
- package/pi-extension/dist/settings/settings-ui.js +593 -563
- package/pi-extension/dist/ui/text-component.js +57 -0
- package/pi-extension/src/cognitive-routing/commands.ts +119 -0
- package/pi-extension/src/cognitive-routing/config.ts +307 -0
- package/pi-extension/src/cognitive-routing/controller.ts +1118 -0
- package/pi-extension/src/cognitive-routing/history.ts +638 -0
- package/pi-extension/src/cognitive-routing/host.ts +172 -0
- package/pi-extension/src/cognitive-routing/runtime.ts +57 -0
- package/pi-extension/src/cognitive-routing/tool.ts +272 -0
- package/pi-extension/src/cognitive-routing/types.ts +89 -0
- package/pi-extension/src/context-virtualization/commands.ts +111 -0
- package/pi-extension/src/context-virtualization/projector.ts +62 -0
- package/pi-extension/src/context-virtualization/runtime.ts +406 -0
- package/pi-extension/src/context-virtualization/state.ts +79 -0
- package/pi-extension/src/context-virtualization/types.ts +92 -0
- package/pi-extension/src/conversation-history/passages.ts +81 -0
- package/pi-extension/src/conversation-history/ranking.ts +155 -0
- package/pi-extension/src/conversation-history/runtime.ts +250 -0
- package/pi-extension/src/conversation-history/search.ts +79 -0
- package/pi-extension/src/conversation-history/types.ts +31 -0
- package/pi-extension/src/freeflow-context/resolver.ts +139 -0
- package/pi-extension/src/freeflow-context/runtime.ts +100 -0
- package/pi-extension/src/freeflow-context/source-projector.ts +106 -0
- package/pi-extension/src/freeflow-context/tool.ts +360 -0
- package/pi-extension/src/freeflow-context/types.ts +70 -0
- package/pi-extension/src/freeflow-context/visibility.ts +53 -0
- package/pi-extension/src/index.ts +553 -101
- package/pi-extension/src/runtime/runtime-context.ts +339 -281
- package/pi-extension/src/runtime/runtime-identity.ts +8 -0
- package/pi-extension/src/settings/settings-tui.ts +93 -0
- package/pi-extension/src/settings/settings-ui.ts +700 -613
- package/pi-extension/src/ui/text-component.ts +66 -0
- package/pi-extension/tests/cognitive-routing/commands.test.js +134 -0
- package/pi-extension/tests/cognitive-routing/controller.test.js +1099 -0
- package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +1071 -0
- package/pi-extension/tests/cognitive-routing/history.test.js +274 -0
- package/pi-extension/tests/cognitive-routing/host-fixture.js +4 -0
- package/pi-extension/tests/cognitive-routing/host-identity.test.js +63 -0
- package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +236 -0
- package/pi-extension/tests/cognitive-routing/tool.test.js +230 -0
- package/pi-extension/tests/context-virtualization/extension.test.js +749 -0
- package/pi-extension/tests/context-virtualization/projector.test.js +110 -0
- package/pi-extension/tests/context-virtualization/runtime.test.js +216 -0
- package/pi-extension/tests/conversation-history/runtime.test.js +98 -0
- package/pi-extension/tests/conversation-history/search.test.js +97 -0
- package/pi-extension/tests/integration/pi-extension.test.js +928 -2047
- package/pi-extension/tests/integration/prompt-architecture.test.js +305 -0
- package/runtime/prompts/cognitive-routing.md +9 -0
- package/runtime/prompts/context-virtualization.md +3 -0
- package/runtime/prompts/conversation-history.md +3 -0
- package/runtime/prompts/core.md +13 -0
- package/runtime/prompts/interaction-contract.md +7 -0
- package/runtime/prompts/skills.md +65 -0
- package/skills/action-selection/SKILL.md +91 -0
- package/skills/bypass/SKILL.md +3 -2
- package/skills/commit-work/SKILL.md +1 -1
- package/skills/commit-work/references/staging-decisions.md +1 -1
- package/skills/decision-gate/SKILL.md +1 -1
- package/skills/design-for-depth/SKILL.md +14 -33
- package/skills/design-for-depth/references/design-pressure-signals.md +1 -1
- package/skills/design-for-depth/references/interface-design-loop.md +1 -1
- package/skills/design-for-depth/references/software-design-philosophy.md +1 -1
- package/skills/diagnose-failure/SKILL.md +20 -14
- package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +1 -3
- package/skills/diagnose-failure/references/{flaky-and-performance.md → flaky-and-performance-diagnosis.md} +1 -1
- package/skills/discuss/SKILL.md +34 -22
- package/skills/discuss/references/discussion-continuity.md +71 -0
- package/skills/evaluate-skill/SKILL.md +5 -5
- package/skills/evaluate-skill/references/{definition-schema.md → evaluation-definition-schema.md} +42 -2
- package/skills/evaluate-skill/references/execution-and-evidence.md +8 -3
- package/skills/evaluate-skill/scripts/lib/definitions.mjs +114 -3
- package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +156 -1
- package/skills/evaluate-skill/scripts/lib/grade.mjs +9 -1
- package/skills/evaluate-skill/scripts/lib/pi.mjs +147 -32
- package/skills/evaluate-skill/scripts/lib/rpc.mjs +4 -4
- package/skills/evaluate-skill/scripts/lib/runner.mjs +36 -14
- package/skills/evaluate-skill/scripts/lib/sandbox.mjs +92 -0
- package/skills/evaluate-skill/scripts/pi-guard.mjs +1 -1
- package/skills/evaluate-skill/scripts/pi-observer.mjs +66 -0
- package/skills/evaluate-skill/scripts/skill-eval.mjs +6 -5
- package/skills/execute-work/SKILL.md +18 -15
- package/skills/execute-work/references/code-practices.md +1 -1
- package/skills/execute-work/references/execute-work-edges.md +42 -0
- package/skills/finish-branch/SKILL.md +2 -2
- package/skills/finish-branch/references/integration-options.md +1 -1
- package/skills/handoff/SKILL.md +3 -3
- package/skills/launch-work/SKILL.md +1 -1
- package/skills/launch-work/references/launch-readiness.md +2 -2
- package/skills/migration-work/SKILL.md +2 -2
- package/skills/migration-work/references/migration-lifecycle.md +1 -1
- package/skills/mode-contract/SKILL.md +32 -10
- package/skills/release-work/SKILL.md +1 -1
- package/skills/release-work/references/release-evidence.md +1 -1
- package/skills/review-artifact/SKILL.md +28 -26
- package/skills/review-work/SKILL.md +28 -26
- package/skills/review-work/references/{reviewer-prompt.md → independent-work-reviewer-contract.md} +1 -1
- package/skills/review-work/references/security-risk-lens.md +1 -1
- package/skills/setup-freeflow/SKILL.md +6 -10
- package/skills/setup-freeflow/references/activation-contract.md +5 -10
- package/skills/setup-freeflow/references/host-setup.md +3 -7
- package/skills/simplify-code/SKILL.md +1 -1
- package/skills/simplify-code/references/simplification-patterns.md +1 -1
- package/skills/tdd/SKILL.md +18 -38
- package/skills/tdd/references/test-design.md +2 -2
- package/skills/track-work/SKILL.md +362 -58
- package/skills/track-work/scripts/lib/codec.mjs +570 -0
- package/skills/track-work/scripts/lib/commands.mjs +568 -0
- package/skills/track-work/scripts/lib/contract.mjs +303 -0
- package/skills/track-work/scripts/lib/model.mjs +506 -0
- package/skills/track-work/scripts/lib/operations.mjs +965 -0
- package/skills/track-work/scripts/lib/record-store.mjs +264 -0
- package/skills/track-work/scripts/lib/result.mjs +37 -0
- package/skills/track-work/scripts/lib/rewrites.mjs +740 -0
- package/skills/track-work/scripts/lib/schema.mjs +277 -0
- package/skills/track-work/scripts/lib/source-inventory.mjs +162 -0
- package/skills/track-work/scripts/lib/views.mjs +443 -0
- package/skills/track-work/scripts/lib/workspace.mjs +134 -0
- package/skills/track-work/scripts/working-record.mjs +122 -0
- package/skills/verify-work/SKILL.md +14 -2
- package/skills/verify-work/references/browser-runtime-evidence.md +1 -1
- package/skills/verify-work/references/integration-evidence.md +1 -1
- package/skills/verify-work/references/performance-evidence.md +1 -1
- package/skills/workflow/SKILL.md +105 -53
- package/skills/workflow/references/domain-skill-composition.md +22 -42
- package/skills/write-plan/SKILL.md +1 -1
- package/skills/write-skill/SKILL.md +3 -2
- package/skills/write-skill/references/progressive-disclosure.md +17 -3
- package/pi-extension/dist/output-router/host-producer-identification.js +0 -232
- package/pi-extension/dist/output-router/native-safety-net.js +0 -216
- package/pi-extension/dist/output-router/observed-tool-routing.js +0 -122
- package/pi-extension/dist/output-router/renderers.js +0 -1023
- package/pi-extension/dist/output-router/router-tools.js +0 -554
- package/pi-extension/dist/output-router/schemas.js +0 -383
- package/pi-extension/dist/output-router/status.js +0 -744
- package/pi-extension/dist/output-router/utils.js +0 -502
- package/pi-extension/src/output-router/host-producer-identification.ts +0 -256
- package/pi-extension/src/output-router/native-safety-net.ts +0 -248
- package/pi-extension/src/output-router/observed-tool-routing.ts +0 -134
- package/pi-extension/src/output-router/renderers.ts +0 -1108
- package/pi-extension/src/output-router/router-tools.ts +0 -579
- package/pi-extension/src/output-router/schemas.ts +0 -387
- package/pi-extension/src/output-router/status.ts +0 -791
- package/pi-extension/src/output-router/utils.ts +0 -554
- package/pi-extension/tests/output-router/observed-producers.test.js +0 -132
- package/pi-extension/tests/output-router/search.test.js +0 -722
- package/router/dist/benchmarks/benchmark-harness.d.ts +0 -35
- package/router/dist/benchmarks/benchmark-harness.js +0 -92
- package/router/dist/benchmarks/benchmarks.d.ts +0 -109
- package/router/dist/benchmarks/benchmarks.js +0 -979
- package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +0 -95
- package/router/dist/benchmarks/codex-qa-benchmarks.js +0 -500
- package/router/dist/benchmarks/command-benchmarks.d.ts +0 -143
- package/router/dist/benchmarks/command-benchmarks.js +0 -698
- package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +0 -82
- package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +0 -773
- package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +0 -132
- package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +0 -1380
- package/router/dist/benchmarks/context-saving-baseline.d.ts +0 -60
- package/router/dist/benchmarks/context-saving-baseline.js +0 -470
- package/router/dist/benchmarks/index-benchmarks.d.ts +0 -103
- package/router/dist/benchmarks/index-benchmarks.js +0 -862
- package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +0 -101
- package/router/dist/benchmarks/storage-policy-benchmarks.js +0 -618
- package/router/dist/benchmarks/vault-index-benchmarks.d.ts +0 -56
- package/router/dist/benchmarks/vault-index-benchmarks.js +0 -322
- package/router/dist/config/config.d.ts +0 -103
- package/router/dist/config/config.js +0 -632
- package/router/dist/config/router-contract.d.ts +0 -10
- package/router/dist/config/router-contract.js +0 -54
- package/router/dist/config/schema.d.ts +0 -27
- package/router/dist/config/schema.js +0 -648
- package/router/dist/config/types.d.ts +0 -573
- package/router/dist/config/types.js +0 -41
- package/router/dist/evidence/bounded-evidence.d.ts +0 -40
- package/router/dist/evidence/bounded-evidence.js +0 -323
- package/router/dist/evidence/evidence-range-selector.d.ts +0 -24
- package/router/dist/evidence/evidence-range-selector.js +0 -232
- package/router/dist/evidence/evidence-search.d.ts +0 -27
- package/router/dist/evidence/evidence-search.js +0 -490
- package/router/dist/evidence/evidence.d.ts +0 -38
- package/router/dist/evidence/evidence.js +0 -200
- package/router/dist/evidence/failure-contracts.d.ts +0 -37
- package/router/dist/evidence/failure-contracts.js +0 -166
- package/router/dist/evidence/line-ranges.d.ts +0 -24
- package/router/dist/evidence/line-ranges.js +0 -22
- package/router/dist/experiments/local-index.d.ts +0 -64
- package/router/dist/experiments/local-index.js +0 -581
- package/router/dist/index.d.ts +0 -30
- package/router/dist/index.js +0 -32
- package/router/dist/local/local-traversal.d.ts +0 -18
- package/router/dist/local/local-traversal.js +0 -334
- package/router/dist/processing/engine.d.ts +0 -132
- package/router/dist/processing/engine.js +0 -533
- package/router/dist/processing/reducers.d.ts +0 -280
- package/router/dist/processing/reducers.js +0 -1673
- package/router/dist/processing/renderers.d.ts +0 -31
- package/router/dist/processing/renderers.js +0 -195
- package/router/dist/processing/scripts.d.ts +0 -91
- package/router/dist/processing/scripts.js +0 -386
- package/router/dist/repo/repo-traversal.d.ts +0 -18
- package/router/dist/repo/repo-traversal.js +0 -245
- package/router/dist/routing/observed-reducers.d.ts +0 -41
- package/router/dist/routing/observed-reducers.js +0 -661
- package/router/dist/routing/observed-routing.d.ts +0 -26
- package/router/dist/routing/observed-routing.js +0 -378
- package/router/dist/routing/parsers.d.ts +0 -16
- package/router/dist/routing/parsers.js +0 -521
- package/router/dist/routing/run-filters.d.ts +0 -60
- package/router/dist/routing/run-filters.js +0 -238
- package/router/dist/routing/run-reducers.d.ts +0 -49
- package/router/dist/routing/run-reducers.js +0 -122
- package/router/dist/sandbox/adapter-roots.d.ts +0 -46
- package/router/dist/sandbox/adapter-roots.js +0 -66
- package/router/dist/sandbox/eryx-python-adapter.d.ts +0 -13
- package/router/dist/sandbox/eryx-python-adapter.js +0 -868
- package/router/dist/sandbox/jq-wasm-adapter.d.ts +0 -9
- package/router/dist/sandbox/jq-wasm-adapter.js +0 -429
- package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +0 -13
- package/router/dist/sandbox/quickjs-wasi-adapter.js +0 -384
- package/router/dist/sandbox/script-sandbox.d.ts +0 -134
- package/router/dist/sandbox/script-sandbox.js +0 -337
- package/router/dist/setup/script-transform-adapters.d.ts +0 -32
- package/router/dist/setup/script-transform-adapters.js +0 -278
- package/router/dist/tools/batch.d.ts +0 -33
- package/router/dist/tools/batch.js +0 -586
- package/router/dist/tools/run.d.ts +0 -62
- package/router/dist/tools/run.js +0 -1304
- package/router/dist/tools/search.d.ts +0 -66
- package/router/dist/tools/search.js +0 -1364
- package/router/dist/transform/engine.d.ts +0 -168
- package/router/dist/transform/engine.js +0 -2072
- package/router/dist/vault/vault-index.d.ts +0 -125
- package/router/dist/vault/vault-index.js +0 -416
- package/router/dist/vault/vault.d.ts +0 -138
- package/router/dist/vault/vault.js +0 -630
- package/runtime/interaction-contract.md +0 -16
- package/skills/discuss/references/checkpoints.md +0 -66
- package/skills/execute-work/references/execution-loop.md +0 -77
- package/skills/output-router/SKILL.md +0 -206
- package/skills/output-router/references/safety-policy.md +0 -40
- package/skills/setup-freeflow/references/output-router-setup.md +0 -110
- package/skills/track-work/references/working-record-schema.md +0 -378
- package/skills/workflow/references/workflow-loop.md +0 -92
- /package/skills/handoff/references/{templates.md → handoff-templates.md} +0 -0
- /package/skills/review-artifact/references/{reviewer-prompt.md → independent-artifact-reviewer-contract.md} +0 -0
|
@@ -1,630 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { mkdir, open, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { dirname, join, resolve } from "node:path";
|
|
5
|
-
import { DEFAULT_VAULT_RETENTION, DEFAULT_VAULT_ROOT } from "../config/config.js";
|
|
6
|
-
import { createLocalVaultIndex, recordVaultIndexFailure } from "./vault-index.js";
|
|
7
|
-
const sessionIndexWriteLocks = new Map();
|
|
8
|
-
const SESSION_INDEX_LOCK_RETRY_MS = 5;
|
|
9
|
-
const SESSION_INDEX_LOCK_TIMEOUT_MS = 5_000;
|
|
10
|
-
export function createVault(options = {}) {
|
|
11
|
-
return {
|
|
12
|
-
root: resolveVaultRoot(options.root ?? DEFAULT_VAULT_ROOT),
|
|
13
|
-
retention: options.retention ?? DEFAULT_VAULT_RETENTION,
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
export function resolveVaultRoot(root) {
|
|
17
|
-
if (root === "~") {
|
|
18
|
-
return homedir();
|
|
19
|
-
}
|
|
20
|
-
if (root.startsWith("~/")) {
|
|
21
|
-
return join(homedir(), root.slice(2));
|
|
22
|
-
}
|
|
23
|
-
return resolve(root);
|
|
24
|
-
}
|
|
25
|
-
export function commandOutputFingerprints(options) {
|
|
26
|
-
const combined = options.combined ?? combineOutputSections(options.stdout, options.stderr);
|
|
27
|
-
return {
|
|
28
|
-
exactSha256: sha256Json({ stdout: options.stdout, stderr: options.stderr, combined }),
|
|
29
|
-
normalizedSha256: sha256Json({
|
|
30
|
-
stdout: normalizeOutputText(options.stdout),
|
|
31
|
-
stderr: normalizeOutputText(options.stderr),
|
|
32
|
-
combined: normalizeOutputText(combined),
|
|
33
|
-
}),
|
|
34
|
-
commandFingerprintSha256: sha256Json({
|
|
35
|
-
command: options.command,
|
|
36
|
-
cwd: options.cwd ?? null,
|
|
37
|
-
executionStatus: options.executionStatus,
|
|
38
|
-
exitCode: options.exitCode,
|
|
39
|
-
}),
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
export function textOutputFingerprints(options) {
|
|
43
|
-
return {
|
|
44
|
-
exactSha256: sha256Text(options.raw),
|
|
45
|
-
normalizedSha256: sha256Text(normalizeOutputText(options.raw)),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
export async function findExactDuplicateCommandOutput(vault, options) {
|
|
49
|
-
const index = await readSessionIndex(vault, options.sessionId);
|
|
50
|
-
for (const outputId of index.outputs) {
|
|
51
|
-
if (outputId === options.excludeOutputId) {
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
const entry = index.records[outputId];
|
|
55
|
-
if (!entry || entry.kind !== "command") {
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
const fingerprints = entry.fingerprints;
|
|
59
|
-
if (
|
|
60
|
-
fingerprints?.exactSha256 === options.fingerprints.exactSha256 &&
|
|
61
|
-
fingerprints.commandFingerprintSha256 === options.fingerprints.commandFingerprintSha256
|
|
62
|
-
) {
|
|
63
|
-
return entry;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
export async function findExactDuplicateTextOutput(vault, options) {
|
|
69
|
-
const index = await readSessionIndex(vault, options.sessionId);
|
|
70
|
-
for (const outputId of index.outputs) {
|
|
71
|
-
if (outputId === options.excludeOutputId) {
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
const entry = index.records[outputId];
|
|
75
|
-
if (!entry || entry.kind !== "text") {
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
if (entry.fingerprints?.exactSha256 === options.fingerprints.exactSha256) {
|
|
79
|
-
return entry;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return undefined;
|
|
83
|
-
}
|
|
84
|
-
export async function storeCommandOutput(vault, options) {
|
|
85
|
-
assertStorablePersistence(options.persistence);
|
|
86
|
-
const createdAt = options.createdAt ?? new Date().toISOString();
|
|
87
|
-
const combined = options.combined ?? combineOutputSections(options.stdout, options.stderr);
|
|
88
|
-
const decisionIds = options.decisionIds ?? [];
|
|
89
|
-
const fingerprints = commandOutputFingerprints({
|
|
90
|
-
command: options.command,
|
|
91
|
-
stdout: options.stdout,
|
|
92
|
-
stderr: options.stderr,
|
|
93
|
-
combined,
|
|
94
|
-
executionStatus: options.executionStatus,
|
|
95
|
-
exitCode: options.exitCode,
|
|
96
|
-
...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
|
|
97
|
-
});
|
|
98
|
-
const payloadHash = sha256Json({
|
|
99
|
-
kind: "command",
|
|
100
|
-
createdAt,
|
|
101
|
-
command: options.command,
|
|
102
|
-
cwd: options.cwd,
|
|
103
|
-
executionStatus: options.executionStatus,
|
|
104
|
-
exitCode: options.exitCode,
|
|
105
|
-
durationMs: options.durationMs,
|
|
106
|
-
stdout: options.stdout,
|
|
107
|
-
stderr: options.stderr,
|
|
108
|
-
combined,
|
|
109
|
-
decisionIds,
|
|
110
|
-
});
|
|
111
|
-
const objectId = objectIdFromHash(payloadHash);
|
|
112
|
-
const outputId = outputIdFromHash(payloadHash);
|
|
113
|
-
const recordId = recordIdFromHash(payloadHash);
|
|
114
|
-
const objectDir = objectDirectory(vault, objectId);
|
|
115
|
-
const paths = {
|
|
116
|
-
meta: join(objectDir, "meta.json"),
|
|
117
|
-
stdout: join(objectDir, "stdout.txt"),
|
|
118
|
-
stderr: join(objectDir, "stderr.txt"),
|
|
119
|
-
combined: join(objectDir, "combined.txt"),
|
|
120
|
-
};
|
|
121
|
-
const record = {
|
|
122
|
-
kind: "command",
|
|
123
|
-
outputId,
|
|
124
|
-
recordId,
|
|
125
|
-
objectId,
|
|
126
|
-
command: options.command,
|
|
127
|
-
createdAt,
|
|
128
|
-
executionStatus: options.executionStatus,
|
|
129
|
-
exitCode: options.exitCode,
|
|
130
|
-
paths,
|
|
131
|
-
lineCounts: {
|
|
132
|
-
stdout: countLines(options.stdout),
|
|
133
|
-
stderr: countLines(options.stderr),
|
|
134
|
-
combined: countLines(combined),
|
|
135
|
-
},
|
|
136
|
-
byteCounts: {
|
|
137
|
-
stdout: byteLength(options.stdout),
|
|
138
|
-
stderr: byteLength(options.stderr),
|
|
139
|
-
combined: byteLength(combined),
|
|
140
|
-
},
|
|
141
|
-
hashes: {
|
|
142
|
-
stdoutSha256: sha256Text(options.stdout),
|
|
143
|
-
stderrSha256: sha256Text(options.stderr),
|
|
144
|
-
combinedSha256: sha256Text(combined),
|
|
145
|
-
},
|
|
146
|
-
fingerprints,
|
|
147
|
-
decisionIds,
|
|
148
|
-
producer: options.producer ?? { kind: "command" },
|
|
149
|
-
persistence: options.persistence ?? exactVaultPersistence(outputId),
|
|
150
|
-
contentHashSha256: payloadHash,
|
|
151
|
-
retention: vault.retention,
|
|
152
|
-
};
|
|
153
|
-
if (options.lineage !== undefined) {
|
|
154
|
-
record.lineage = options.lineage;
|
|
155
|
-
}
|
|
156
|
-
const commandExpiresAt = expiresAt(createdAt, vault.retention);
|
|
157
|
-
if (commandExpiresAt !== undefined) {
|
|
158
|
-
record.expiresAt = commandExpiresAt;
|
|
159
|
-
}
|
|
160
|
-
if (options.cwd !== undefined) {
|
|
161
|
-
record.cwd = options.cwd;
|
|
162
|
-
}
|
|
163
|
-
if (options.durationMs !== undefined) {
|
|
164
|
-
record.durationMs = options.durationMs;
|
|
165
|
-
}
|
|
166
|
-
await mkdir(objectDir, { recursive: true });
|
|
167
|
-
await writeFile(paths.stdout, options.stdout, "utf8");
|
|
168
|
-
await writeFile(paths.stderr, options.stderr, "utf8");
|
|
169
|
-
await writeFile(paths.combined, combined, "utf8");
|
|
170
|
-
await writeJson(paths.meta, record);
|
|
171
|
-
await addRecordToSessionIndex(vault, options.sessionId, record);
|
|
172
|
-
await indexVaultRecordAfterAppend(vault, options.sessionId, record, combined, "combined");
|
|
173
|
-
return record;
|
|
174
|
-
}
|
|
175
|
-
export async function storeTextOutput(vault, options) {
|
|
176
|
-
assertStorablePersistence(options.persistence);
|
|
177
|
-
const createdAt = options.createdAt ?? new Date().toISOString();
|
|
178
|
-
const decisionIds = options.decisionIds ?? [];
|
|
179
|
-
const fingerprints = textOutputFingerprints({ raw: options.raw });
|
|
180
|
-
const payloadHash = sha256Json({
|
|
181
|
-
kind: "text",
|
|
182
|
-
createdAt,
|
|
183
|
-
sourceKind: options.sourceKind,
|
|
184
|
-
raw: options.raw,
|
|
185
|
-
decisionIds,
|
|
186
|
-
});
|
|
187
|
-
const objectId = objectIdFromHash(payloadHash);
|
|
188
|
-
const outputId = outputIdFromHash(payloadHash);
|
|
189
|
-
const recordId = recordIdFromHash(payloadHash);
|
|
190
|
-
const objectDir = objectDirectory(vault, objectId);
|
|
191
|
-
const paths = {
|
|
192
|
-
meta: join(objectDir, "meta.json"),
|
|
193
|
-
raw: join(objectDir, "raw.txt"),
|
|
194
|
-
};
|
|
195
|
-
const record = {
|
|
196
|
-
kind: "text",
|
|
197
|
-
outputId,
|
|
198
|
-
recordId,
|
|
199
|
-
objectId,
|
|
200
|
-
sourceKind: options.sourceKind,
|
|
201
|
-
createdAt,
|
|
202
|
-
paths,
|
|
203
|
-
lineCounts: { raw: countLines(options.raw) },
|
|
204
|
-
byteCounts: { raw: byteLength(options.raw) },
|
|
205
|
-
hashes: { rawSha256: sha256Text(options.raw) },
|
|
206
|
-
fingerprints,
|
|
207
|
-
decisionIds,
|
|
208
|
-
producer: options.producer ?? producerForTextSourceKind(options.sourceKind),
|
|
209
|
-
persistence: options.persistence ?? exactVaultPersistence(outputId),
|
|
210
|
-
contentHashSha256: payloadHash,
|
|
211
|
-
retention: vault.retention,
|
|
212
|
-
};
|
|
213
|
-
if (options.lineage !== undefined) {
|
|
214
|
-
record.lineage = options.lineage;
|
|
215
|
-
}
|
|
216
|
-
const textExpiresAt = expiresAt(createdAt, vault.retention);
|
|
217
|
-
if (textExpiresAt !== undefined) {
|
|
218
|
-
record.expiresAt = textExpiresAt;
|
|
219
|
-
}
|
|
220
|
-
await mkdir(objectDir, { recursive: true });
|
|
221
|
-
await writeFile(paths.raw, options.raw, "utf8");
|
|
222
|
-
await writeJson(paths.meta, record);
|
|
223
|
-
await addRecordToSessionIndex(vault, options.sessionId, record);
|
|
224
|
-
await indexVaultRecordAfterAppend(vault, options.sessionId, record, options.raw, "raw");
|
|
225
|
-
return record;
|
|
226
|
-
}
|
|
227
|
-
export async function storeMetadataOutput(vault, options) {
|
|
228
|
-
assertStorablePersistence(options.persistence);
|
|
229
|
-
const createdAt = options.createdAt ?? new Date().toISOString();
|
|
230
|
-
const decisionIds = options.decisionIds ?? [];
|
|
231
|
-
const payloadHash = sha256Json({
|
|
232
|
-
kind: "metadata",
|
|
233
|
-
createdAt,
|
|
234
|
-
sourceKind: options.sourceKind,
|
|
235
|
-
rawLineCount: options.rawLineCount,
|
|
236
|
-
rawByteCount: options.rawByteCount,
|
|
237
|
-
rawSha256: options.rawSha256,
|
|
238
|
-
metadata: options.metadata,
|
|
239
|
-
decisionIds,
|
|
240
|
-
});
|
|
241
|
-
const objectId = objectIdFromHash(payloadHash);
|
|
242
|
-
const outputId = outputIdFromHash(payloadHash);
|
|
243
|
-
const recordId = recordIdFromHash(payloadHash);
|
|
244
|
-
const objectDir = objectDirectory(vault, objectId);
|
|
245
|
-
const paths = {
|
|
246
|
-
meta: join(objectDir, "meta.json"),
|
|
247
|
-
};
|
|
248
|
-
const record = {
|
|
249
|
-
kind: "metadata",
|
|
250
|
-
outputId,
|
|
251
|
-
recordId,
|
|
252
|
-
objectId,
|
|
253
|
-
sourceKind: options.sourceKind,
|
|
254
|
-
createdAt,
|
|
255
|
-
paths,
|
|
256
|
-
lineCounts: { raw: options.rawLineCount },
|
|
257
|
-
byteCounts: { raw: options.rawByteCount },
|
|
258
|
-
hashes: options.rawSha256 !== undefined ? { rawSha256: options.rawSha256 } : {},
|
|
259
|
-
decisionIds,
|
|
260
|
-
producer: options.producer ?? producerForTextSourceKind(options.sourceKind),
|
|
261
|
-
persistence: options.persistence ?? metadataOnlyPersistence(outputId),
|
|
262
|
-
contentHashSha256: payloadHash,
|
|
263
|
-
retention: vault.retention,
|
|
264
|
-
};
|
|
265
|
-
if (options.metadata !== undefined) {
|
|
266
|
-
record.metadata = options.metadata;
|
|
267
|
-
}
|
|
268
|
-
if (options.lineage !== undefined) {
|
|
269
|
-
record.lineage = options.lineage;
|
|
270
|
-
}
|
|
271
|
-
const metadataExpiresAt = expiresAt(createdAt, vault.retention);
|
|
272
|
-
if (metadataExpiresAt !== undefined) {
|
|
273
|
-
record.expiresAt = metadataExpiresAt;
|
|
274
|
-
}
|
|
275
|
-
await mkdir(objectDir, { recursive: true });
|
|
276
|
-
await writeJson(paths.meta, record);
|
|
277
|
-
await addRecordToSessionIndex(vault, options.sessionId, record);
|
|
278
|
-
await indexVaultRecordAfterAppend(vault, options.sessionId, record, undefined);
|
|
279
|
-
return record;
|
|
280
|
-
}
|
|
281
|
-
export async function storeRepoFileReference(vault, options) {
|
|
282
|
-
assertStorablePersistence(options.persistence);
|
|
283
|
-
const createdAt = options.createdAt ?? new Date().toISOString();
|
|
284
|
-
const decisionIds = options.decisionIds ?? [];
|
|
285
|
-
const payloadHash = sha256Json({
|
|
286
|
-
kind: "repo-file",
|
|
287
|
-
createdAt,
|
|
288
|
-
path: options.path,
|
|
289
|
-
hashSha256: options.hashSha256,
|
|
290
|
-
decisionIds,
|
|
291
|
-
});
|
|
292
|
-
const objectId = objectIdFromHash(payloadHash);
|
|
293
|
-
const outputId = outputIdFromHash(payloadHash);
|
|
294
|
-
const recordId = recordIdFromHash(payloadHash);
|
|
295
|
-
const objectDir = objectDirectory(vault, objectId);
|
|
296
|
-
const paths = {
|
|
297
|
-
meta: join(objectDir, "meta.json"),
|
|
298
|
-
};
|
|
299
|
-
const record = {
|
|
300
|
-
kind: "repo-file",
|
|
301
|
-
outputId,
|
|
302
|
-
recordId,
|
|
303
|
-
objectId,
|
|
304
|
-
path: options.path,
|
|
305
|
-
paths,
|
|
306
|
-
decisionIds,
|
|
307
|
-
createdAt,
|
|
308
|
-
producer: options.producer ?? { kind: "repo" },
|
|
309
|
-
persistence: options.persistence ?? metadataOnlyPersistence(),
|
|
310
|
-
contentHashSha256: payloadHash,
|
|
311
|
-
retention: vault.retention,
|
|
312
|
-
};
|
|
313
|
-
if (options.lineage !== undefined) {
|
|
314
|
-
record.lineage = options.lineage;
|
|
315
|
-
}
|
|
316
|
-
const repoExpiresAt = expiresAt(createdAt, vault.retention);
|
|
317
|
-
if (repoExpiresAt !== undefined) {
|
|
318
|
-
record.expiresAt = repoExpiresAt;
|
|
319
|
-
}
|
|
320
|
-
if (options.hashSha256 !== undefined) {
|
|
321
|
-
record.hashSha256 = options.hashSha256;
|
|
322
|
-
}
|
|
323
|
-
await mkdir(objectDir, { recursive: true });
|
|
324
|
-
await writeJson(paths.meta, record);
|
|
325
|
-
await addRecordToSessionIndex(vault, options.sessionId, record);
|
|
326
|
-
await indexVaultRecordAfterAppend(vault, options.sessionId, record, undefined);
|
|
327
|
-
return record;
|
|
328
|
-
}
|
|
329
|
-
export async function readVaultRecord(vault, sessionId, outputId) {
|
|
330
|
-
const index = await readSessionIndex(vault, sessionId);
|
|
331
|
-
const entry = index.records[outputId];
|
|
332
|
-
if (!entry) {
|
|
333
|
-
throw new Error(`No vault output found for ${outputId} in session ${sessionId}.`);
|
|
334
|
-
}
|
|
335
|
-
return readJson(join(objectDirectory(vault, entry.objectId), "meta.json"));
|
|
336
|
-
}
|
|
337
|
-
export async function readOutputText(vault, sessionId, outputId, stream) {
|
|
338
|
-
const record = await readVaultRecord(vault, sessionId, outputId);
|
|
339
|
-
const path = streamPath(record, stream);
|
|
340
|
-
return readFile(path, "utf8");
|
|
341
|
-
}
|
|
342
|
-
export async function readOutputLines(vault, options) {
|
|
343
|
-
if (!Number.isInteger(options.startLine) || !Number.isInteger(options.endLine)) {
|
|
344
|
-
throw new Error("Line ranges must use integer line numbers.");
|
|
345
|
-
}
|
|
346
|
-
if (options.startLine < 1 || options.endLine < options.startLine) {
|
|
347
|
-
throw new Error("Line ranges must be 1-based and endLine must be greater than or equal to startLine.");
|
|
348
|
-
}
|
|
349
|
-
const text = await readOutputText(vault, options.sessionId, options.outputId, options.stream);
|
|
350
|
-
return splitLines(text)
|
|
351
|
-
.slice(options.startLine - 1, options.endLine)
|
|
352
|
-
.join("\n");
|
|
353
|
-
}
|
|
354
|
-
export async function readSessionIndex(vault, sessionId) {
|
|
355
|
-
const path = sessionIndexPath(vault, sessionId);
|
|
356
|
-
try {
|
|
357
|
-
return await readJson(path);
|
|
358
|
-
} catch (error) {
|
|
359
|
-
const code = typeof error === "object" && error !== null && "code" in error ? error.code : undefined;
|
|
360
|
-
if (code !== "ENOENT") {
|
|
361
|
-
throw error;
|
|
362
|
-
}
|
|
363
|
-
const now = new Date().toISOString();
|
|
364
|
-
return emptySessionIndex(sessionId, now);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
function assertStorablePersistence(persistence) {
|
|
368
|
-
if (persistence?.recoverability === "none") {
|
|
369
|
-
throw new Error("Cannot store output with persistence recoverability none; skip vault storage instead.");
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
async function indexVaultRecordAfterAppend(vault, sessionId, record, text, stream) {
|
|
373
|
-
try {
|
|
374
|
-
const index = createLocalVaultIndex(vault);
|
|
375
|
-
const metadata = {
|
|
376
|
-
sessionId,
|
|
377
|
-
...(stream !== undefined ? { stream } : {}),
|
|
378
|
-
};
|
|
379
|
-
await index.indexRecord(record, text, metadata);
|
|
380
|
-
} catch (error) {
|
|
381
|
-
await recordVaultIndexFailure(vault, error).catch(() => undefined);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
async function addRecordToSessionIndex(vault, sessionId, record) {
|
|
385
|
-
await withSessionIndexWriteLock(vault, sessionId, async () => {
|
|
386
|
-
const index = await readSessionIndex(vault, sessionId);
|
|
387
|
-
const updatedAt = new Date().toISOString();
|
|
388
|
-
const entry = {
|
|
389
|
-
outputId: record.outputId,
|
|
390
|
-
recordId: record.recordId,
|
|
391
|
-
objectId: record.objectId,
|
|
392
|
-
kind: record.kind,
|
|
393
|
-
createdAt: record.createdAt,
|
|
394
|
-
producer: record.producer,
|
|
395
|
-
persistence: record.persistence,
|
|
396
|
-
};
|
|
397
|
-
if (record.lineage !== undefined) {
|
|
398
|
-
entry.lineage = record.lineage;
|
|
399
|
-
}
|
|
400
|
-
if (record.kind === "command") {
|
|
401
|
-
entry.executionStatus = record.executionStatus;
|
|
402
|
-
}
|
|
403
|
-
if (record.fingerprints !== undefined) {
|
|
404
|
-
entry.fingerprints = record.fingerprints;
|
|
405
|
-
}
|
|
406
|
-
index.updatedAt = updatedAt;
|
|
407
|
-
index.records[record.outputId] = entry;
|
|
408
|
-
addUnique(index.outputs, record.outputId);
|
|
409
|
-
if (record.kind === "command") {
|
|
410
|
-
addToExecutionGroup(index, record.executionStatus, record.outputId);
|
|
411
|
-
}
|
|
412
|
-
await writeJsonAtomic(sessionIndexPath(vault, sessionId), index);
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
async function withSessionIndexWriteLock(vault, sessionId, operation) {
|
|
416
|
-
const key = `${vault.root}:${safeSegment(sessionId)}`;
|
|
417
|
-
const previous = sessionIndexWriteLocks.get(key) ?? Promise.resolve();
|
|
418
|
-
let releaseCurrent;
|
|
419
|
-
const current = new Promise((resolve) => {
|
|
420
|
-
releaseCurrent = resolve;
|
|
421
|
-
});
|
|
422
|
-
const chained = previous.catch(() => undefined).then(() => current);
|
|
423
|
-
sessionIndexWriteLocks.set(key, chained);
|
|
424
|
-
await previous.catch(() => undefined);
|
|
425
|
-
try {
|
|
426
|
-
return await withSessionIndexFileLock(vault, sessionId, operation);
|
|
427
|
-
} finally {
|
|
428
|
-
releaseCurrent();
|
|
429
|
-
if (sessionIndexWriteLocks.get(key) === chained) {
|
|
430
|
-
sessionIndexWriteLocks.delete(key);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
async function withSessionIndexFileLock(vault, sessionId, operation) {
|
|
435
|
-
const path = sessionIndexLockPath(vault, sessionId);
|
|
436
|
-
await mkdir(dirname(path), { recursive: true });
|
|
437
|
-
const startedAt = Date.now();
|
|
438
|
-
let handle;
|
|
439
|
-
while (handle === undefined) {
|
|
440
|
-
try {
|
|
441
|
-
handle = await open(path, "wx");
|
|
442
|
-
await handle.writeFile(`${process.pid}\n${new Date().toISOString()}\n`, "utf8");
|
|
443
|
-
} catch (error) {
|
|
444
|
-
const code = typeof error === "object" && error !== null && "code" in error ? error.code : undefined;
|
|
445
|
-
if (code !== "EEXIST") {
|
|
446
|
-
throw error;
|
|
447
|
-
}
|
|
448
|
-
if (Date.now() - startedAt > SESSION_INDEX_LOCK_TIMEOUT_MS) {
|
|
449
|
-
throw new Error(`Timed out waiting for vault session index lock for ${safeSegment(sessionId)}.`);
|
|
450
|
-
}
|
|
451
|
-
await sleep(SESSION_INDEX_LOCK_RETRY_MS);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
try {
|
|
455
|
-
return await operation();
|
|
456
|
-
} finally {
|
|
457
|
-
await handle.close().catch(() => undefined);
|
|
458
|
-
await unlink(path).catch(() => undefined);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
function addToExecutionGroup(index, status, outputId) {
|
|
462
|
-
if (status === "success") {
|
|
463
|
-
addUnique(index.successful, outputId);
|
|
464
|
-
} else if (status === "failed") {
|
|
465
|
-
addUnique(index.failed, outputId);
|
|
466
|
-
} else if (status === "timed_out") {
|
|
467
|
-
addUnique(index.timedOut, outputId);
|
|
468
|
-
} else if (status === "cancelled") {
|
|
469
|
-
addUnique(index.cancelled, outputId);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
function emptySessionIndex(sessionId, now) {
|
|
473
|
-
return {
|
|
474
|
-
version: 1,
|
|
475
|
-
sessionId: safeSegment(sessionId),
|
|
476
|
-
createdAt: now,
|
|
477
|
-
updatedAt: now,
|
|
478
|
-
outputs: [],
|
|
479
|
-
records: {},
|
|
480
|
-
successful: [],
|
|
481
|
-
failed: [],
|
|
482
|
-
timedOut: [],
|
|
483
|
-
cancelled: [],
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
|
-
function streamPath(record, stream) {
|
|
487
|
-
if (record.kind === "command") {
|
|
488
|
-
if (stream === "raw") {
|
|
489
|
-
throw new Error("Command records do not have a raw stream. Use stdout, stderr, or combined.");
|
|
490
|
-
}
|
|
491
|
-
return record.paths[stream];
|
|
492
|
-
}
|
|
493
|
-
if (record.kind === "text") {
|
|
494
|
-
if (stream !== "raw") {
|
|
495
|
-
throw new Error("Text records only have a raw stream.");
|
|
496
|
-
}
|
|
497
|
-
return record.paths.raw;
|
|
498
|
-
}
|
|
499
|
-
if (record.kind === "metadata") {
|
|
500
|
-
throw new Error("Metadata only records store no raw stream.");
|
|
501
|
-
}
|
|
502
|
-
throw new Error("Repo file reference records store metadata only and have no raw stream.");
|
|
503
|
-
}
|
|
504
|
-
function sessionIndexPath(vault, sessionId) {
|
|
505
|
-
return join(vault.root, "sessions", safeSegment(sessionId), "index.json");
|
|
506
|
-
}
|
|
507
|
-
function sessionIndexLockPath(vault, sessionId) {
|
|
508
|
-
return `${sessionIndexPath(vault, sessionId)}.lock`;
|
|
509
|
-
}
|
|
510
|
-
function objectDirectory(vault, objectId) {
|
|
511
|
-
return join(vault.root, "objects", objectId);
|
|
512
|
-
}
|
|
513
|
-
function objectIdFromHash(hash) {
|
|
514
|
-
return `sha256_${hash}`;
|
|
515
|
-
}
|
|
516
|
-
function outputIdFromHash(hash) {
|
|
517
|
-
return `ffout_${hash.slice(0, 24)}`;
|
|
518
|
-
}
|
|
519
|
-
function recordIdFromHash(hash) {
|
|
520
|
-
return `ffrec_${hash.slice(0, 24)}`;
|
|
521
|
-
}
|
|
522
|
-
function exactVaultPersistence(outputId) {
|
|
523
|
-
return {
|
|
524
|
-
status: "vaulted",
|
|
525
|
-
recoverability: "exact",
|
|
526
|
-
recoveryOutputId: outputId,
|
|
527
|
-
outputId,
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
function metadataOnlyPersistence(outputId) {
|
|
531
|
-
return {
|
|
532
|
-
status: "metadata_only",
|
|
533
|
-
recoverability: "metadata_only",
|
|
534
|
-
...(outputId !== undefined ? { outputId } : {}),
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
|
-
function producerForTextSourceKind(sourceKind) {
|
|
538
|
-
if (sourceKind === "native") {
|
|
539
|
-
return { kind: "native" };
|
|
540
|
-
}
|
|
541
|
-
if (sourceKind === "script") {
|
|
542
|
-
return { kind: "script" };
|
|
543
|
-
}
|
|
544
|
-
if (sourceKind === "mcp") {
|
|
545
|
-
return { kind: "mcp" };
|
|
546
|
-
}
|
|
547
|
-
if (sourceKind === "web") {
|
|
548
|
-
return { kind: "web" };
|
|
549
|
-
}
|
|
550
|
-
if (sourceKind === "fetch") {
|
|
551
|
-
return { kind: "fetch" };
|
|
552
|
-
}
|
|
553
|
-
if (sourceKind === "code_search") {
|
|
554
|
-
return { kind: "code_search" };
|
|
555
|
-
}
|
|
556
|
-
if (sourceKind === "transform") {
|
|
557
|
-
return { kind: "transform" };
|
|
558
|
-
}
|
|
559
|
-
return { kind: "other" };
|
|
560
|
-
}
|
|
561
|
-
function combineOutputSections(stdout, stderr) {
|
|
562
|
-
if (stdout.length === 0) {
|
|
563
|
-
return stderr;
|
|
564
|
-
}
|
|
565
|
-
if (stderr.length === 0) {
|
|
566
|
-
return stdout;
|
|
567
|
-
}
|
|
568
|
-
return `[stdout]\n${stdout}\n[stderr]\n${stderr}`;
|
|
569
|
-
}
|
|
570
|
-
function countLines(text) {
|
|
571
|
-
if (text.length === 0) {
|
|
572
|
-
return 0;
|
|
573
|
-
}
|
|
574
|
-
return splitLines(text).length;
|
|
575
|
-
}
|
|
576
|
-
function splitLines(text) {
|
|
577
|
-
return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
578
|
-
}
|
|
579
|
-
function byteLength(text) {
|
|
580
|
-
return Buffer.byteLength(text, "utf8");
|
|
581
|
-
}
|
|
582
|
-
function sha256Text(text) {
|
|
583
|
-
return createHash("sha256").update(text).digest("hex");
|
|
584
|
-
}
|
|
585
|
-
function normalizeOutputText(text) {
|
|
586
|
-
return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").trimEnd();
|
|
587
|
-
}
|
|
588
|
-
function sha256Json(value) {
|
|
589
|
-
return sha256Text(JSON.stringify(value));
|
|
590
|
-
}
|
|
591
|
-
function expiresAt(createdAt, retention) {
|
|
592
|
-
if (retention.strategy !== "ttl") {
|
|
593
|
-
return undefined;
|
|
594
|
-
}
|
|
595
|
-
const createdTime = new Date(createdAt).getTime();
|
|
596
|
-
if (!Number.isFinite(createdTime)) {
|
|
597
|
-
return undefined;
|
|
598
|
-
}
|
|
599
|
-
return new Date(createdTime + retention.ttlDays * 24 * 60 * 60 * 1_000).toISOString();
|
|
600
|
-
}
|
|
601
|
-
function addUnique(items, item) {
|
|
602
|
-
if (!items.includes(item)) {
|
|
603
|
-
items.push(item);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
function safeSegment(value) {
|
|
607
|
-
const safe = value.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
608
|
-
return safe.length > 0 ? safe : "session";
|
|
609
|
-
}
|
|
610
|
-
async function writeJson(path, value) {
|
|
611
|
-
await mkdir(dirname(path), { recursive: true });
|
|
612
|
-
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
613
|
-
}
|
|
614
|
-
async function writeJsonAtomic(path, value) {
|
|
615
|
-
await mkdir(dirname(path), { recursive: true });
|
|
616
|
-
const tempPath = join(dirname(path), `.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.json`);
|
|
617
|
-
try {
|
|
618
|
-
await writeFile(tempPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
619
|
-
await rename(tempPath, path);
|
|
620
|
-
} catch (error) {
|
|
621
|
-
await unlink(tempPath).catch(() => undefined);
|
|
622
|
-
throw error;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
function sleep(ms) {
|
|
626
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
627
|
-
}
|
|
628
|
-
async function readJson(path) {
|
|
629
|
-
return JSON.parse(await readFile(path, "utf8"));
|
|
630
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# Freeflow Interaction Contract
|
|
2
|
-
|
|
3
|
-
Work as a collaborative senior engineer. Interpret the whole user turn before
|
|
4
|
-
acting. Use evidence, disagree when warranted, and correct yourself when
|
|
5
|
-
evidence changes.
|
|
6
|
-
|
|
7
|
-
Answer questions without inferring action. Treat criticism, examples,
|
|
8
|
-
hypotheses, and tentative ideas as discussion, not authorization. If a question
|
|
9
|
-
affects consent or method, answer and wait unless the user clearly authorizes
|
|
10
|
-
action.
|
|
11
|
-
|
|
12
|
-
For consequential work, establish enough shared understanding for
|
|
13
|
-
the next sound action. If a brief discussion is likely to materially improve
|
|
14
|
-
alignment on the outcome, boundaries, tradeoffs, approach, or acceptance,
|
|
15
|
-
recommend it, name the question, and wait. Otherwise choose reversible local
|
|
16
|
-
details and proceed. Do not ask merely because more detail is possible.
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# Discussion Checkpoints
|
|
2
|
-
|
|
3
|
-
Read this when closing or preserving a compact discussion checkpoint.
|
|
4
|
-
|
|
5
|
-
A checkpoint records enough shared understanding to continue or choose the next action. It does not mean discussion is permanently complete.
|
|
6
|
-
|
|
7
|
-
## Keep It In Chat When Possible
|
|
8
|
-
|
|
9
|
-
Keep the checkpoint in the conversation when the current context can safely continue. Use only the fields that help:
|
|
10
|
-
|
|
11
|
-
```text
|
|
12
|
-
Goal or question:
|
|
13
|
-
Current understanding:
|
|
14
|
-
Settled:
|
|
15
|
-
Tentative:
|
|
16
|
-
Open:
|
|
17
|
-
Evidence or alternatives that matter:
|
|
18
|
-
Approved or proposed checkpoints, if any:
|
|
19
|
-
Recommended or accepted next action:
|
|
20
|
-
Authority source or approval needed:
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Do not turn a checkpoint into a transcript, questionnaire, frozen architecture, automatic spec, or complete plan.
|
|
24
|
-
|
|
25
|
-
## Preserve Selected Checkpoints
|
|
26
|
-
|
|
27
|
-
When discussion proposes or approves an execution checkpoint, preserve only what later action needs:
|
|
28
|
-
|
|
29
|
-
```text
|
|
30
|
-
Status: Proposed | Approved
|
|
31
|
-
Type:
|
|
32
|
-
Purpose:
|
|
33
|
-
Due boundary:
|
|
34
|
-
Conditions:
|
|
35
|
-
Approval scope:
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Do not infer authorization from a recommendation. Keep local commit approval separate from push or integration, and keep migration, deprecation, release, and launch separately controlled.
|
|
39
|
-
|
|
40
|
-
## Preserve It When Needed
|
|
41
|
-
|
|
42
|
-
Choose the artifact by the information that must survive:
|
|
43
|
-
|
|
44
|
-
- **Working Record:** evolving task and checkpoint state, slices, decisions, evidence, history, and next action. Read [Track Work](../../track-work/SKILL.md).
|
|
45
|
-
- **Write Spec:** stable accepted content needs a separate durable artifact. Read [Write Spec](../../write-spec/SKILL.md).
|
|
46
|
-
- **Write Plan:** a stable ordered strategy and its selected checkpoints need a separate artifact. Read [Write Plan](../../write-plan/SKILL.md).
|
|
47
|
-
- **ADR:** a surprising, hard-to-reverse repo-level decision needs durable rationale.
|
|
48
|
-
- **Handoff:** one point-in-time continuation state must be transferred to another context or owner.
|
|
49
|
-
- **Domain documentation:** stable terminology or domain meaning belongs in an existing glossary or domain source.
|
|
50
|
-
|
|
51
|
-
Do not create an artifact merely because discussion occurred. Prefer an existing repository convention; ask before inventing a destination whose authority, durability, or ownership matters.
|
|
52
|
-
|
|
53
|
-
## Re-Entry Checkpoint
|
|
54
|
-
|
|
55
|
-
When new evidence reopens discussion, preserve only the affected change:
|
|
56
|
-
|
|
57
|
-
```text
|
|
58
|
-
New evidence:
|
|
59
|
-
Invalidated assumption:
|
|
60
|
-
Still valid:
|
|
61
|
-
Decision, slice, or artifact affected:
|
|
62
|
-
Question or experiment now needed:
|
|
63
|
-
Authority or decision needed:
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Do not restart from zero or rewrite unaffected decisions and work.
|