@hassangameryt/freeflow 0.5.0 → 0.7.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 -2
- package/.codex-plugin/plugin.json +4 -5
- package/README.md +141 -163
- package/capabilities/cognitive-routing/SKILL.md +387 -0
- package/capabilities/context-virtualization/SKILL.md +103 -0
- package/capabilities/conversation-history/SKILL.md +53 -0
- package/command-surface.json +6 -37
- package/hooks/freeflow-runtime-context.mjs +50 -541
- package/hooks/hooks.json +1 -26
- package/hooks/tests/check-runtime-context-hook.sh +114 -308
- package/package.json +27 -17
- package/pi-extension/dist/cognitive-routing/commands.js +96 -0
- package/pi-extension/dist/cognitive-routing/config.js +232 -0
- package/pi-extension/dist/cognitive-routing/controller.js +1292 -0
- package/pi-extension/dist/cognitive-routing/history.js +451 -0
- package/pi-extension/dist/cognitive-routing/host.js +188 -0
- package/pi-extension/dist/cognitive-routing/pi-routing-session.js +414 -0
- package/pi-extension/dist/cognitive-routing/pi-session-control.js +320 -0
- package/pi-extension/dist/cognitive-routing/runtime.js +43 -0
- package/pi-extension/dist/cognitive-routing/tool.js +212 -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 +564 -129
- package/pi-extension/dist/runtime/runtime-context.js +357 -470
- 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 +572 -826
- package/pi-extension/dist/ui/text-component.js +57 -0
- package/pi-extension/src/cognitive-routing/commands.ts +129 -0
- package/pi-extension/src/cognitive-routing/config.ts +303 -0
- package/pi-extension/src/cognitive-routing/controller.ts +1628 -0
- package/pi-extension/src/cognitive-routing/history.ts +650 -0
- package/pi-extension/src/cognitive-routing/host.ts +266 -0
- package/pi-extension/src/cognitive-routing/pi-routing-session.ts +538 -0
- package/pi-extension/src/cognitive-routing/pi-session-control.ts +444 -0
- package/pi-extension/src/cognitive-routing/runtime.ts +57 -0
- package/pi-extension/src/cognitive-routing/tool.ts +273 -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 +597 -135
- package/pi-extension/src/runtime/runtime-context.ts +373 -491
- 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 +679 -910
- 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 +1128 -0
- package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +1142 -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/pi-normal-lifecycle.test.js +434 -0
- package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +174 -0
- package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +91 -0
- package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +279 -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/mode-free-core.test.js +218 -0
- package/pi-extension/tests/integration/pi-extension.test.js +532 -3701
- package/pi-extension/tests/integration/prompt-architecture.test.js +350 -0
- package/pi-extension/tests/integration/unaffected-regressions.test.js +411 -0
- package/runtime/prompts/cognitive-routing.md +10 -0
- package/runtime/prompts/context-virtualization.md +3 -0
- package/runtime/prompts/conversation-history.md +3 -0
- package/runtime/prompts/core.md +84 -0
- package/runtime/prompts/interaction-contract.md +11 -0
- package/skills/action-selection/SKILL.md +140 -0
- package/skills/action-selection/references/trajectory-stalls.md +71 -0
- package/skills/bypass/SKILL.md +109 -38
- package/skills/commit-work/SKILL.md +94 -55
- package/skills/commit-work/references/git-state-edges.md +94 -0
- package/skills/commit-work/references/simple-push.md +92 -0
- package/skills/commit-work/references/staging-decisions.md +79 -46
- package/skills/decision-gate/SKILL.md +80 -22
- package/skills/design-for-depth/SKILL.md +206 -60
- package/skills/design-for-depth/references/design-pressure-signals.md +108 -240
- package/skills/design-for-depth/references/interface-design-loop.md +77 -97
- package/skills/design-for-depth/references/module-and-dependency-design.md +147 -0
- package/skills/design-for-depth/references/software-design-philosophy.md +128 -174
- package/skills/design-for-depth/references/state-and-failure-boundaries.md +136 -0
- package/skills/diagnose-failure/SKILL.md +200 -48
- package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +18 -6
- package/skills/diagnose-failure/references/{flaky-and-performance.md → flaky-and-performance-diagnosis.md} +13 -5
- package/skills/discuss/SKILL.md +119 -65
- package/skills/discuss/references/discussion-continuity.md +123 -0
- package/skills/evaluate-skill/SKILL.md +166 -52
- package/skills/evaluate-skill/references/{definition-schema.md → evaluation-definition-schema.md} +43 -3
- package/skills/evaluate-skill/references/evaluation-design.md +56 -30
- package/skills/evaluate-skill/references/execution-and-evidence.md +8 -3
- package/skills/evaluate-skill/references/review-and-revision.md +62 -21
- 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 +99 -57
- package/skills/execute-work/references/code-practices.md +4 -4
- package/skills/execute-work/references/execute-work-edges.md +62 -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/release-work/SKILL.md +1 -1
- package/skills/release-work/references/release-evidence.md +1 -1
- package/skills/review-artifact/SKILL.md +129 -66
- package/skills/review-artifact/references/adjudicate-artifact-review.md +101 -0
- package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +159 -0
- package/skills/review-work/SKILL.md +113 -68
- package/skills/review-work/references/adjudicate-work-review.md +97 -0
- package/skills/review-work/references/independent-work-reviewer-contract.md +139 -0
- package/skills/review-work/references/security-risk-lens.md +18 -16
- package/skills/setup-freeflow/SKILL.md +16 -17
- package/skills/setup-freeflow/references/activation-contract.md +24 -30
- package/skills/setup-freeflow/references/host-setup.md +6 -7
- package/skills/simplify-code/SKILL.md +97 -39
- package/skills/simplify-code/references/simplification-patterns.md +1 -1
- package/skills/tdd/SKILL.md +100 -53
- package/skills/tdd/references/test-design.md +3 -3
- package/skills/track-work/SKILL.md +195 -58
- package/skills/track-work/references/working-record-format.md +515 -0
- package/skills/track-work/scripts/lib/document.mjs +713 -0
- package/skills/track-work/scripts/lib/format.mjs +166 -0
- package/skills/track-work/scripts/lib/store.mjs +233 -0
- package/skills/track-work/scripts/lib/transitions.mjs +790 -0
- package/skills/track-work/scripts/lib/views.mjs +40 -0
- package/skills/track-work/scripts/working-record.mjs +316 -0
- package/skills/verify-work/SKILL.md +57 -19
- package/skills/verify-work/references/browser-runtime-evidence.md +2 -2
- package/skills/verify-work/references/integration-evidence.md +1 -7
- package/skills/verify-work/references/performance-evidence.md +2 -2
- package/skills/workflow/SKILL.md +137 -55
- package/skills/workflow/references/domain-skill-composition.md +26 -40
- package/skills/write-plan/SKILL.md +1 -1
- package/skills/write-skill/SKILL.md +218 -61
- package/skills/write-skill/references/activation-boundaries.md +63 -21
- package/skills/write-skill/references/agent-first-instructions.md +61 -27
- package/skills/write-skill/references/progressive-disclosure.md +97 -33
- package/skills/write-skill/references/skill-dependency-graphs.md +190 -0
- package/capabilities/output-router/SKILL.md +0 -206
- package/capabilities/output-router/references/safety-policy.md +0 -40
- package/capabilities/output-router/references/setup.md +0 -110
- 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/mode-contract/SKILL.md +0 -104
- package/skills/review-artifact/references/reviewer-prompt.md +0 -148
- package/skills/review-work/references/reviewer-prompt.md +0 -134
- 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
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import crypto from "node:crypto";
|
|
4
3
|
import fs from "node:fs";
|
|
5
4
|
import path from "node:path";
|
|
6
5
|
import process from "node:process";
|
|
@@ -8,16 +7,10 @@ import { fileURLToPath } from "node:url";
|
|
|
8
7
|
|
|
9
8
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
10
9
|
const PLUGIN_ROOT = path.resolve(SCRIPT_DIR, "..");
|
|
11
|
-
const VALID_MODES = new Set(["conversation", "workflow", "strict-workflow"]);
|
|
12
|
-
const SESSION_STATE_VERSION = 1;
|
|
13
|
-
const SESSION_STATE_MAX_AGE_MS = 90 * 24 * 60 * 60 * 1000;
|
|
14
|
-
const CLEAR_TRANSFER_MAX_AGE_MS = 60 * 1000;
|
|
15
10
|
|
|
16
11
|
function readStdinJson() {
|
|
17
12
|
const raw = fs.readFileSync(0, "utf8").trim();
|
|
18
|
-
if (!raw) {
|
|
19
|
-
return { input: {}, raw: "" };
|
|
20
|
-
}
|
|
13
|
+
if (!raw) return { input: {}, raw: "" };
|
|
21
14
|
|
|
22
15
|
try {
|
|
23
16
|
return { input: JSON.parse(raw), raw };
|
|
@@ -28,7 +21,8 @@ function readStdinJson() {
|
|
|
28
21
|
|
|
29
22
|
function readText(filePath) {
|
|
30
23
|
try {
|
|
31
|
-
|
|
24
|
+
const text = fs.readFileSync(filePath, "utf8").trim();
|
|
25
|
+
return text || null;
|
|
32
26
|
} catch {
|
|
33
27
|
return null;
|
|
34
28
|
}
|
|
@@ -38,31 +32,23 @@ function findWorkspaceRoot(cwd) {
|
|
|
38
32
|
let current = path.resolve(cwd || process.cwd());
|
|
39
33
|
|
|
40
34
|
while (true) {
|
|
41
|
-
if (fs.existsSync(path.join(current, ".git")))
|
|
42
|
-
return current;
|
|
43
|
-
}
|
|
35
|
+
if (fs.existsSync(path.join(current, ".git"))) return current;
|
|
44
36
|
|
|
45
37
|
const parent = path.dirname(current);
|
|
46
|
-
if (parent === current)
|
|
47
|
-
return path.resolve(cwd || process.cwd());
|
|
48
|
-
}
|
|
38
|
+
if (parent === current) return path.resolve(cwd || process.cwd());
|
|
49
39
|
current = parent;
|
|
50
40
|
}
|
|
51
41
|
}
|
|
52
42
|
|
|
53
|
-
function loadRuntimeContext(
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const interactionContract = includeInteractionContract
|
|
57
|
-
? readText(path.join(PLUGIN_ROOT, "runtime", "interaction-contract.md"))
|
|
58
|
-
: null;
|
|
59
|
-
const workflowSkill = includeSkills ? readText(path.join(PLUGIN_ROOT, "skills", "workflow", "SKILL.md")) : null;
|
|
43
|
+
function loadRuntimeContext() {
|
|
44
|
+
const corePrompt = readText(path.join(PLUGIN_ROOT, "runtime", "prompts", "core.md"));
|
|
45
|
+
const interactionContract = readText(path.join(PLUGIN_ROOT, "runtime", "prompts", "interaction-contract.md"));
|
|
60
46
|
|
|
61
|
-
if (
|
|
62
|
-
throw new Error("Freeflow runtime
|
|
47
|
+
if (!corePrompt || !interactionContract) {
|
|
48
|
+
throw new Error("Freeflow core runtime prompt files are missing.");
|
|
63
49
|
}
|
|
64
50
|
|
|
65
|
-
return {
|
|
51
|
+
return { corePrompt, interactionContract };
|
|
66
52
|
}
|
|
67
53
|
|
|
68
54
|
function readConfig(root) {
|
|
@@ -80,12 +66,10 @@ function readConfig(root) {
|
|
|
80
66
|
repositoryValid: repository.valid,
|
|
81
67
|
localExists: local.exists,
|
|
82
68
|
localValid,
|
|
83
|
-
error:
|
|
69
|
+
error: repository.valid ? local.error : repository.error,
|
|
84
70
|
enabled,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
defaultMode: core.config.defaultMode,
|
|
88
|
-
sources: core.sources,
|
|
71
|
+
contextVirtualizationEnabled: enabled && core.config.contextVirtualization,
|
|
72
|
+
conversationHistoryEnabled: enabled && core.config.conversationHistory,
|
|
89
73
|
};
|
|
90
74
|
}
|
|
91
75
|
|
|
@@ -120,554 +104,89 @@ function isRecord(value) {
|
|
|
120
104
|
}
|
|
121
105
|
|
|
122
106
|
function validateCoreConfigFields(value) {
|
|
123
|
-
if (Object.hasOwn(value, "defaultMode") && !VALID_MODES.has(value.defaultMode)) {
|
|
124
|
-
return "defaultMode must be conversation, workflow, or strict-workflow";
|
|
125
|
-
}
|
|
126
107
|
if (Object.hasOwn(value, "enabled") && typeof value.enabled !== "boolean") {
|
|
127
108
|
return "enabled must be a boolean";
|
|
128
109
|
}
|
|
129
|
-
if (Object.hasOwn(value, "
|
|
130
|
-
return "
|
|
110
|
+
if (Object.hasOwn(value, "contextVirtualization") && typeof value.contextVirtualization !== "boolean") {
|
|
111
|
+
return "contextVirtualization must be a boolean";
|
|
131
112
|
}
|
|
132
|
-
if (Object.hasOwn(value, "
|
|
133
|
-
|
|
134
|
-
return "skills must be an object";
|
|
135
|
-
}
|
|
136
|
-
if (Object.hasOwn(value.skills, "enabled") && typeof value.skills.enabled !== "boolean") {
|
|
137
|
-
return "skills.enabled must be a boolean";
|
|
138
|
-
}
|
|
113
|
+
if (Object.hasOwn(value, "conversationHistory") && typeof value.conversationHistory !== "boolean") {
|
|
114
|
+
return "conversationHistory must be a boolean";
|
|
139
115
|
}
|
|
140
116
|
return null;
|
|
141
117
|
}
|
|
142
118
|
|
|
143
119
|
function isValidSetupConfig(value) {
|
|
144
|
-
if (!isRecord(value))
|
|
145
|
-
return "repository config must be a JSON object";
|
|
146
|
-
}
|
|
120
|
+
if (!isRecord(value)) return "repository config must be a JSON object";
|
|
147
121
|
|
|
148
122
|
const allowedKeys = new Set([
|
|
149
123
|
"enabled",
|
|
150
|
-
"
|
|
151
|
-
"
|
|
152
|
-
"skills",
|
|
124
|
+
"contextVirtualization",
|
|
125
|
+
"conversationHistory",
|
|
153
126
|
"outputRouter",
|
|
154
127
|
"observedRouting",
|
|
155
128
|
"scriptTransform",
|
|
129
|
+
"cognitiveRouting",
|
|
156
130
|
]);
|
|
157
131
|
if (!Object.keys(value).every((key) => allowedKeys.has(key))) {
|
|
158
132
|
return "repository config contains unsupported top-level keys";
|
|
159
133
|
}
|
|
160
134
|
|
|
161
135
|
const coreError = validateCoreConfigFields(value);
|
|
162
|
-
if (coreError)
|
|
163
|
-
return coreError;
|
|
164
|
-
}
|
|
136
|
+
if (coreError) return coreError;
|
|
165
137
|
|
|
166
138
|
for (const key of ["outputRouter", "observedRouting", "scriptTransform"]) {
|
|
167
|
-
if (Object.hasOwn(value, key) && !isRecord(value[key])) {
|
|
168
|
-
return `${key} must be an object`;
|
|
169
|
-
}
|
|
139
|
+
if (Object.hasOwn(value, key) && !isRecord(value[key])) return `${key} must be an object`;
|
|
170
140
|
}
|
|
171
141
|
return null;
|
|
172
142
|
}
|
|
173
143
|
|
|
174
144
|
function isValidLocalConfig(value) {
|
|
175
|
-
if (!isRecord(value))
|
|
176
|
-
return "local config must be a JSON object";
|
|
177
|
-
}
|
|
145
|
+
if (!isRecord(value)) return "local config must be a JSON object";
|
|
178
146
|
|
|
179
|
-
const allowedKeys = new Set([
|
|
147
|
+
const allowedKeys = new Set([
|
|
148
|
+
"enabled",
|
|
149
|
+
"processing",
|
|
150
|
+
"cognitiveRouting",
|
|
151
|
+
"contextVirtualization",
|
|
152
|
+
"conversationHistory",
|
|
153
|
+
]);
|
|
180
154
|
if (!Object.keys(value).every((key) => allowedKeys.has(key))) {
|
|
181
155
|
return "local config contains unsupported top-level keys";
|
|
182
156
|
}
|
|
183
157
|
return validateCoreConfigFields(value);
|
|
184
158
|
}
|
|
185
159
|
|
|
186
|
-
function hasOwn(value, key) {
|
|
187
|
-
return Object.hasOwn(value, key);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
160
|
function resolveLayeredValue(repository, local, key, fallback) {
|
|
191
|
-
if (hasOwn(local, key))
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (hasOwn(repository, key)) {
|
|
195
|
-
return { value: repository[key], source: "repository" };
|
|
196
|
-
}
|
|
197
|
-
return { value: fallback, source: "builtin" };
|
|
161
|
+
if (Object.hasOwn(local, key)) return local[key];
|
|
162
|
+
if (Object.hasOwn(repository, key)) return repository[key];
|
|
163
|
+
return fallback;
|
|
198
164
|
}
|
|
199
165
|
|
|
200
166
|
function resolveCoreConfig(repository, local) {
|
|
201
|
-
const enabled = resolveLayeredValue(repository, local, "enabled", true);
|
|
202
|
-
const interactionContract = resolveLayeredValue(repository, local, "interactionContract", true);
|
|
203
|
-
const defaultMode = resolveLayeredValue(repository, local, "defaultMode", "workflow");
|
|
204
|
-
const repositorySkills = isRecord(repository.skills) ? repository.skills : {};
|
|
205
|
-
const localSkills = isRecord(local.skills) ? local.skills : {};
|
|
206
|
-
const skillsEnabled = resolveLayeredValue(repositorySkills, localSkills, "enabled", true);
|
|
207
|
-
|
|
208
167
|
return {
|
|
209
168
|
config: {
|
|
210
|
-
enabled: enabled
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
defaultMode: defaultMode.value,
|
|
169
|
+
enabled: resolveLayeredValue(repository, local, "enabled", true),
|
|
170
|
+
contextVirtualization: resolveLayeredValue(repository, local, "contextVirtualization", false),
|
|
171
|
+
conversationHistory: resolveLayeredValue(repository, local, "conversationHistory", false),
|
|
214
172
|
},
|
|
215
|
-
sources: {
|
|
216
|
-
enabled: enabled.source,
|
|
217
|
-
interactionContract: interactionContract.source,
|
|
218
|
-
skillsEnabled: skillsEnabled.source,
|
|
219
|
-
defaultMode: defaultMode.source,
|
|
220
|
-
},
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function hostName() {
|
|
225
|
-
return process.env.PLUGIN_DATA ? "codex" : "claude";
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function pluginDataRoot() {
|
|
229
|
-
const configured = process.env.PLUGIN_DATA || process.env.CLAUDE_PLUGIN_DATA;
|
|
230
|
-
return configured ? path.resolve(configured) : null;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function sessionStatePath(sessionId) {
|
|
234
|
-
const dataRoot = pluginDataRoot();
|
|
235
|
-
if (!dataRoot || typeof sessionId !== "string" || sessionId.length === 0) {
|
|
236
|
-
return null;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const key = crypto.createHash("sha256").update(`${hostName()}\0${sessionId}`).digest("hex");
|
|
240
|
-
return path.join(dataRoot, "session-modes", hostName(), `${key}.json`);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function workspaceKey(root) {
|
|
244
|
-
return crypto.createHash("sha256").update(path.resolve(root)).digest("hex");
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function claudeProcessKey() {
|
|
248
|
-
const processId = process.env.CLAUDE_PID;
|
|
249
|
-
if (typeof processId !== "string" || processId.length === 0) {
|
|
250
|
-
return null;
|
|
251
|
-
}
|
|
252
|
-
return crypto.createHash("sha256").update(processId).digest("hex");
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function clearTransferPath(root) {
|
|
256
|
-
const dataRoot = pluginDataRoot();
|
|
257
|
-
const processKey = claudeProcessKey();
|
|
258
|
-
if (!dataRoot || hostName() !== "claude" || !processKey) {
|
|
259
|
-
return null;
|
|
260
|
-
}
|
|
261
|
-
return path.join(dataRoot, "session-modes", "claude-clear", `${workspaceKey(root)}-${processKey}.json`);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function writeJsonAtomic(filePath, value) {
|
|
265
|
-
const directory = path.dirname(filePath);
|
|
266
|
-
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
267
|
-
const temporaryPath = `${filePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
268
|
-
try {
|
|
269
|
-
fs.writeFileSync(temporaryPath, `${JSON.stringify(value)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
270
|
-
fs.renameSync(temporaryPath, filePath);
|
|
271
|
-
} catch (error) {
|
|
272
|
-
try {
|
|
273
|
-
fs.unlinkSync(temporaryPath);
|
|
274
|
-
} catch {
|
|
275
|
-
// Best-effort cleanup of a failed atomic write.
|
|
276
|
-
}
|
|
277
|
-
throw error;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function readSessionMode(sessionId) {
|
|
282
|
-
const filePath = sessionStatePath(sessionId);
|
|
283
|
-
if (!filePath) {
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
try {
|
|
288
|
-
const state = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
289
|
-
if (state.version !== SESSION_STATE_VERSION || !VALID_MODES.has(state.mode)) {
|
|
290
|
-
return null;
|
|
291
|
-
}
|
|
292
|
-
return state.mode;
|
|
293
|
-
} catch {
|
|
294
|
-
return null;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function writeSessionMode(sessionId, mode) {
|
|
299
|
-
const filePath = sessionStatePath(sessionId);
|
|
300
|
-
if (!filePath) {
|
|
301
|
-
return false;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
writeJsonAtomic(filePath, {
|
|
305
|
-
version: SESSION_STATE_VERSION,
|
|
306
|
-
mode,
|
|
307
|
-
updatedAt: new Date().toISOString(),
|
|
308
|
-
});
|
|
309
|
-
return true;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function clearSessionMode(sessionId) {
|
|
313
|
-
const filePath = sessionStatePath(sessionId);
|
|
314
|
-
if (!filePath) {
|
|
315
|
-
return false;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
try {
|
|
319
|
-
fs.unlinkSync(filePath);
|
|
320
|
-
} catch (error) {
|
|
321
|
-
if (error?.code !== "ENOENT") {
|
|
322
|
-
throw error;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
return true;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
function stageClearTransfer(input) {
|
|
329
|
-
if (hostName() !== "claude" || input.reason !== "clear") {
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const root = findWorkspaceRoot(input.cwd || process.cwd());
|
|
334
|
-
const mode = readSessionMode(input.session_id);
|
|
335
|
-
const filePath = clearTransferPath(root);
|
|
336
|
-
if (!filePath) {
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if (!mode) {
|
|
341
|
-
try {
|
|
342
|
-
fs.unlinkSync(filePath);
|
|
343
|
-
} catch (error) {
|
|
344
|
-
if (error?.code !== "ENOENT") {
|
|
345
|
-
throw error;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
writeJsonAtomic(filePath, {
|
|
352
|
-
version: SESSION_STATE_VERSION,
|
|
353
|
-
mode,
|
|
354
|
-
sourceSessionKey: crypto.createHash("sha256").update(String(input.session_id)).digest("hex"),
|
|
355
|
-
createdAt: new Date().toISOString(),
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
function consumeClearTransfer(input, root) {
|
|
360
|
-
if (hostName() !== "claude" || input.source !== "clear") {
|
|
361
|
-
return null;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const filePath = clearTransferPath(root);
|
|
365
|
-
if (!filePath) {
|
|
366
|
-
return null;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const claimedPath = `${filePath}.${process.pid}.${crypto.randomUUID()}.claimed`;
|
|
370
|
-
try {
|
|
371
|
-
fs.renameSync(filePath, claimedPath);
|
|
372
|
-
} catch (error) {
|
|
373
|
-
if (error?.code === "ENOENT") {
|
|
374
|
-
return null;
|
|
375
|
-
}
|
|
376
|
-
return null;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
let state;
|
|
380
|
-
try {
|
|
381
|
-
state = JSON.parse(fs.readFileSync(claimedPath, "utf8"));
|
|
382
|
-
} catch {
|
|
383
|
-
return null;
|
|
384
|
-
} finally {
|
|
385
|
-
try {
|
|
386
|
-
fs.unlinkSync(claimedPath);
|
|
387
|
-
} catch {
|
|
388
|
-
// The atomic claim already prevents reuse; cleanup is best-effort.
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
if (!isRecord(state)) {
|
|
393
|
-
return null;
|
|
394
|
-
}
|
|
395
|
-
const createdAt = Date.parse(state.createdAt);
|
|
396
|
-
if (
|
|
397
|
-
state.version !== SESSION_STATE_VERSION ||
|
|
398
|
-
!VALID_MODES.has(state.mode) ||
|
|
399
|
-
!Number.isFinite(createdAt) ||
|
|
400
|
-
Date.now() - createdAt > CLEAR_TRANSFER_MAX_AGE_MS ||
|
|
401
|
-
Date.now() < createdAt
|
|
402
|
-
) {
|
|
403
|
-
return null;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
return writeSessionMode(input.session_id, state.mode) ? state.mode : null;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
function cleanupExpiredSessionModes() {
|
|
410
|
-
const root = pluginDataRoot();
|
|
411
|
-
if (!root) {
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const stateRoot = path.join(root, "session-modes");
|
|
416
|
-
const cutoff = Date.now() - SESSION_STATE_MAX_AGE_MS;
|
|
417
|
-
for (const host of ["claude", "codex", "claude-clear"]) {
|
|
418
|
-
const directory = path.join(stateRoot, host);
|
|
419
|
-
let entries = [];
|
|
420
|
-
try {
|
|
421
|
-
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
422
|
-
} catch {
|
|
423
|
-
continue;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
for (const entry of entries) {
|
|
427
|
-
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
428
|
-
continue;
|
|
429
|
-
}
|
|
430
|
-
const filePath = path.join(directory, entry.name);
|
|
431
|
-
try {
|
|
432
|
-
if (fs.statSync(filePath).mtimeMs < cutoff) {
|
|
433
|
-
fs.unlinkSync(filePath);
|
|
434
|
-
}
|
|
435
|
-
} catch {
|
|
436
|
-
// Cleanup is best-effort and must not prevent runtime delivery.
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
function sourceLabel(source) {
|
|
443
|
-
if (source === "local") {
|
|
444
|
-
return "personal";
|
|
445
|
-
}
|
|
446
|
-
return source;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
function resolveRuntimeState(input) {
|
|
450
|
-
const root = findWorkspaceRoot(input.cwd || process.cwd());
|
|
451
|
-
const config = readConfig(root);
|
|
452
|
-
const restoredMode = config.valid && config.enabled ? consumeClearTransfer(input, root) : null;
|
|
453
|
-
const sessionMode = config.valid && config.enabled ? restoredMode || readSessionMode(input.session_id) : null;
|
|
454
|
-
const resolvedMode = sessionMode || config.defaultMode;
|
|
455
|
-
|
|
456
|
-
return {
|
|
457
|
-
root,
|
|
458
|
-
config,
|
|
459
|
-
sessionMode,
|
|
460
|
-
resolvedMode,
|
|
461
|
-
effectiveMode: config.enabled && config.skillsEnabled ? resolvedMode : null,
|
|
462
|
-
modeSource: sessionMode ? "session override" : "configured default",
|
|
463
173
|
};
|
|
464
174
|
}
|
|
465
175
|
|
|
466
|
-
function
|
|
467
|
-
|
|
468
|
-
return [
|
|
469
|
-
"",
|
|
470
|
-
"## Conversation Mode Boundary",
|
|
471
|
-
"",
|
|
472
|
-
"Conversation mode is active and read-only.",
|
|
473
|
-
"",
|
|
474
|
-
"Do not call write, edit, or mutating tools. Do not create, delete, commit, push, or change repository, system, external, or durable state.",
|
|
475
|
-
"",
|
|
476
|
-
"A mutation request does not switch mode, and an execution skill does not override this boundary. Explain that conversation mode is read-only and ask the user to switch to workflow or strict-workflow.",
|
|
477
|
-
];
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
if (mode === "strict-workflow") {
|
|
481
|
-
return [
|
|
482
|
-
"",
|
|
483
|
-
"## Strict Workflow Overlay",
|
|
484
|
-
"",
|
|
485
|
-
"Strict Workflow is active. Use the adaptive Workflow, but increase decision, evidence, verification, and checkpoint pressure at high-risk or hard-to-reverse boundaries.",
|
|
486
|
-
"",
|
|
487
|
-
"For work affecting security, privacy, billing, data loss, migrations, public interfaces, compatibility, deployment, or architecture:",
|
|
488
|
-
"",
|
|
489
|
-
"- stop for any user-owned choice or source conflict;",
|
|
490
|
-
"- inspect the relevant risk surface before crossing the boundary;",
|
|
491
|
-
"- select only artifacts, checkpoints, and independent review that materially reduce risk;",
|
|
492
|
-
"- verify at the affected boundary before claiming success.",
|
|
493
|
-
"",
|
|
494
|
-
"Do not manufacture ceremony for low-risk, reversible work. Strict Workflow does not authorize mutation, bypass safety, or make every implementation detail a user decision.",
|
|
495
|
-
];
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
return [];
|
|
176
|
+
function runtimeStateContext() {
|
|
177
|
+
return ["# Freeflow Runtime State", "", "Freeflow: active"].join("\n");
|
|
499
178
|
}
|
|
500
179
|
|
|
501
|
-
function
|
|
502
|
-
|
|
503
|
-
const lines = [`Configured default: \`${config.defaultMode}\` (${sourceLabel(config.sources.defaultMode)})`];
|
|
504
|
-
|
|
505
|
-
if (sessionChange === "cleared") {
|
|
506
|
-
lines.push("Session override: cleared");
|
|
507
|
-
} else if (state.sessionMode) {
|
|
508
|
-
lines.push(`Session override: \`${state.sessionMode}\``);
|
|
509
|
-
} else {
|
|
510
|
-
lines.push("Session override: none");
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
if (state.effectiveMode) {
|
|
514
|
-
lines.push(`Effective mode: \`${state.effectiveMode}\` (${state.modeSource}, active)`);
|
|
515
|
-
} else {
|
|
516
|
-
lines.push(`Resolved mode: \`${state.resolvedMode}\` (dormant because Skills are disabled)`);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
return lines;
|
|
520
|
-
}
|
|
180
|
+
function renderSessionStart(config) {
|
|
181
|
+
if (!config.valid || !config.enabled) return "";
|
|
521
182
|
|
|
522
|
-
|
|
523
|
-
return [
|
|
524
|
-
`Interaction Contract: ${config.interactionContractEnabled ? "enabled" : "disabled"}`,
|
|
525
|
-
`Skills: ${config.skillsEnabled ? "enabled" : "disabled"}`,
|
|
526
|
-
];
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
function renderSessionStart(state) {
|
|
530
|
-
const { config } = state;
|
|
531
|
-
if (!config.valid) {
|
|
532
|
-
return "";
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (!config.enabled) {
|
|
536
|
-
return [
|
|
537
|
-
"# Freeflow Disabled",
|
|
538
|
-
"",
|
|
539
|
-
`Configured but inactive: \`enabled\` is false (${sourceLabel(config.sources.enabled)}).`,
|
|
540
|
-
"No Freeflow interaction, skill, or mode guidance is effective.",
|
|
541
|
-
].join("\n");
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
const { interactionContract, workflowSkill } = loadRuntimeContext({
|
|
545
|
-
interactionContract: config.interactionContractEnabled,
|
|
546
|
-
skills: config.skillsEnabled,
|
|
547
|
-
});
|
|
548
|
-
const context = [
|
|
549
|
-
"# Freeflow Runtime Context",
|
|
550
|
-
"",
|
|
551
|
-
"Runtime delivery: confirmed for this lifecycle-hook invocation.",
|
|
552
|
-
"This context guides behavior only; user instructions, repository instructions, and host safety or approval policy retain precedence.",
|
|
553
|
-
"",
|
|
554
|
-
"## Effective State",
|
|
555
|
-
"",
|
|
556
|
-
...stateLines(state),
|
|
557
|
-
...capabilityLines(config),
|
|
558
|
-
];
|
|
559
|
-
|
|
560
|
-
if (config.interactionContractEnabled) {
|
|
561
|
-
context.push("", interactionContract.trim());
|
|
562
|
-
}
|
|
563
|
-
if (config.skillsEnabled) {
|
|
564
|
-
context.push("", "# Freeflow Workflow Bootstrap", "", workflowSkill.trim());
|
|
565
|
-
}
|
|
566
|
-
if (state.effectiveMode) {
|
|
567
|
-
context.push(...modeOverlayGuidance(state.effectiveMode));
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
return context.join("\n");
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
function normalizePrompt(prompt) {
|
|
574
|
-
return prompt
|
|
575
|
-
.trim()
|
|
576
|
-
.replace(/[.!]+$/u, "")
|
|
577
|
-
.replace(/\s+/gu, " ");
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
function parseModeControl(prompt) {
|
|
581
|
-
if (typeof prompt !== "string" || prompt.length > 300 || /\?\s*$/u.test(prompt.trim())) {
|
|
582
|
-
return null;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
const normalized = normalizePrompt(prompt);
|
|
586
|
-
const nativeSet = normalized.match(
|
|
587
|
-
/^(?:\/freeflow mode|\$mode-contract|\/(?:freeflow:)?mode-contract) (conversation|workflow|strict-workflow)$/iu,
|
|
588
|
-
);
|
|
589
|
-
if (nativeSet) {
|
|
590
|
-
return { action: "set", mode: nativeSet[1].toLowerCase() };
|
|
591
|
-
}
|
|
592
|
-
if (/^(?:\/freeflow mode|\$mode-contract|\/(?:freeflow:)?mode-contract) reset$/iu.test(normalized)) {
|
|
593
|
-
return { action: "reset" };
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
const setPatterns = [
|
|
597
|
-
/^switch to (conversation|workflow|strict-workflow)(?: mode)?$/iu,
|
|
598
|
-
/^switch (?:this|the) session to (conversation|workflow|strict-workflow)(?: mode)?$/iu,
|
|
599
|
-
/^use (conversation|workflow|strict-workflow)(?: mode)? for (?:this|the) session$/iu,
|
|
600
|
-
/^(?:change|set) (?:the )?(?:current|active|session) mode to (conversation|workflow|strict-workflow)$/iu,
|
|
601
|
-
/^switch (?:the )?(?:current|active|session) mode to (conversation|workflow|strict-workflow)$/iu,
|
|
602
|
-
];
|
|
603
|
-
for (const pattern of setPatterns) {
|
|
604
|
-
const match = normalized.match(pattern);
|
|
605
|
-
if (match) {
|
|
606
|
-
return { action: "set", mode: match[1].toLowerCase() };
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
if (/^(?:reset|clear|remove) (?:the )?(?:current |active )?session mode(?: override)?$/iu.test(normalized)) {
|
|
611
|
-
return { action: "reset" };
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
return null;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
function renderPromptModeChange(state, sessionChange) {
|
|
618
|
-
const lines = [
|
|
619
|
-
"# Freeflow Mode Update",
|
|
620
|
-
"",
|
|
621
|
-
...stateLines(state, sessionChange),
|
|
622
|
-
"",
|
|
623
|
-
"This user-operated session control changed host-managed state before this model request. It changes workflow behavior, not mutation authority, host permissions, or accepted scope.",
|
|
624
|
-
"Acknowledge the effective mode briefly. Do not claim that configured defaults changed.",
|
|
625
|
-
];
|
|
626
|
-
if (state.effectiveMode) {
|
|
627
|
-
lines.push(...modeOverlayGuidance(state.effectiveMode));
|
|
628
|
-
}
|
|
629
|
-
return lines.join("\n");
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
function handleUserPromptSubmit(input) {
|
|
633
|
-
const control = parseModeControl(input.prompt);
|
|
634
|
-
if (!control) {
|
|
635
|
-
return "";
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
const before = resolveRuntimeState(input);
|
|
639
|
-
if (!before.config.valid || !before.config.enabled) {
|
|
640
|
-
return "";
|
|
641
|
-
}
|
|
642
|
-
if (!before.config.skillsEnabled) {
|
|
643
|
-
return [
|
|
644
|
-
"# Freeflow Mode Update Unavailable",
|
|
645
|
-
"",
|
|
646
|
-
`Resolved mode remains \`${before.resolvedMode}\`, but no Freeflow mode is effective because Skills are disabled.`,
|
|
647
|
-
"Do not claim that the session mode changed.",
|
|
648
|
-
].join("\n");
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
if (control.action === "set") {
|
|
652
|
-
if (!writeSessionMode(input.session_id, control.mode)) {
|
|
653
|
-
return "Freeflow could not establish a session mode override because this host invocation supplied no writable plugin data or session identifier. Do not claim that the mode changed.";
|
|
654
|
-
}
|
|
655
|
-
const state = resolveRuntimeState(input);
|
|
656
|
-
return renderPromptModeChange(state, "set");
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
if (!clearSessionMode(input.session_id)) {
|
|
660
|
-
return "Freeflow could not clear the session mode override because this host invocation supplied no writable plugin data or session identifier. Do not claim that the mode changed.";
|
|
661
|
-
}
|
|
662
|
-
const state = resolveRuntimeState(input);
|
|
663
|
-
return renderPromptModeChange(state, "cleared");
|
|
183
|
+
const { corePrompt, interactionContract } = loadRuntimeContext();
|
|
184
|
+
return [corePrompt.trim(), interactionContract.trim(), runtimeStateContext()].filter(Boolean).join("\n\n");
|
|
664
185
|
}
|
|
665
186
|
|
|
666
187
|
function shouldHandle(eventName) {
|
|
667
|
-
if (process.env.FREEFLOW_DISABLE_RUNTIME_CONTEXT === "1")
|
|
668
|
-
|
|
669
|
-
}
|
|
670
|
-
return eventName === "SessionStart" || eventName === "UserPromptSubmit" || eventName === "SessionEnd";
|
|
188
|
+
if (process.env.FREEFLOW_DISABLE_RUNTIME_CONTEXT === "1") return false;
|
|
189
|
+
return eventName === "SessionStart";
|
|
671
190
|
}
|
|
672
191
|
|
|
673
192
|
function emitAdditionalContext(eventName, additionalContext) {
|
|
@@ -685,21 +204,11 @@ function main() {
|
|
|
685
204
|
const { input, raw } = readStdinJson();
|
|
686
205
|
const eventName = process.argv[2] || input.hook_event_name || "";
|
|
687
206
|
|
|
688
|
-
if ((!raw && !eventName) || !shouldHandle(eventName))
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
207
|
+
if ((!raw && !eventName) || !shouldHandle(eventName)) return;
|
|
691
208
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
return;
|
|
696
|
-
}
|
|
697
|
-
const additionalContext =
|
|
698
|
-
eventName === "SessionStart" ? renderSessionStart(resolveRuntimeState(input)) : handleUserPromptSubmit(input);
|
|
699
|
-
if (!additionalContext.trim()) {
|
|
700
|
-
return;
|
|
701
|
-
}
|
|
702
|
-
emitAdditionalContext(eventName, additionalContext);
|
|
209
|
+
const root = findWorkspaceRoot(input.cwd || process.cwd());
|
|
210
|
+
const additionalContext = renderSessionStart(readConfig(root));
|
|
211
|
+
if (additionalContext.trim()) emitAdditionalContext(eventName, additionalContext);
|
|
703
212
|
}
|
|
704
213
|
|
|
705
214
|
try {
|