@hassangameryt/freeflow 0.7.3 → 0.8.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 +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/NOTICE +14 -0
- package/README.md +26 -31
- package/capabilities/cognitive-routing/SKILL.md +106 -161
- package/capabilities/cognitive-routing/references/both-mode.md +36 -0
- package/capabilities/cognitive-routing/references/executor-mode.md +48 -0
- package/capabilities/cognitive-routing/references/helper-mode.md +47 -0
- package/capabilities/compaction/SKILL.md +49 -0
- package/capabilities/compaction/references/summary-format.md +239 -0
- package/command-surface.json +4 -0
- package/gemini-extension.json +1 -1
- package/hooks/shared/runtime-context.mjs +28 -30
- package/licenses/Apache-2.0.txt +55 -0
- package/package.json +22 -9
- package/pi-extension/README.md +41 -0
- package/pi-extension/dist/cognitive-routing/assembler.js +393 -0
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/config.js +5 -2
- package/pi-extension/dist/cognitive-routing/economics.js +45 -0
- package/pi-extension/dist/cognitive-routing/event-schema.js +388 -0
- package/pi-extension/dist/{session-sources/events.js → cognitive-routing/event-store.js} +103 -40
- package/pi-extension/dist/cognitive-routing/gate.js +241 -0
- package/pi-extension/dist/cognitive-routing/handlers.js +899 -0
- package/pi-extension/dist/cognitive-routing/handoffs.js +350 -0
- package/pi-extension/dist/cognitive-routing/model-control.js +467 -0
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/projection.js +229 -56
- package/pi-extension/dist/cognitive-routing/provenance.js +81 -0
- package/pi-extension/dist/cognitive-routing/runtime.js +396 -0
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/schemas.js +43 -7
- package/pi-extension/dist/cognitive-routing/session.js +221 -0
- package/pi-extension/dist/{session-sources → cognitive-routing}/sources.js +99 -85
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/state.js +37 -378
- package/pi-extension/dist/cognitive-routing/status.js +80 -0
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/tools.js +12 -12
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/types.js +5 -11
- package/pi-extension/dist/compaction/carry.js +191 -0
- package/pi-extension/dist/compaction/config.js +22 -0
- package/pi-extension/dist/compaction/controller.js +471 -0
- package/pi-extension/dist/compaction/harness.js +77 -0
- package/pi-extension/dist/compaction/results.js +158 -0
- package/pi-extension/dist/compaction/thresholds.js +35 -0
- package/pi-extension/dist/compaction/tool.js +57 -0
- package/pi-extension/dist/host/branch.js +13 -0
- package/pi-extension/dist/host/canonical.js +12 -0
- package/pi-extension/dist/host/catalog.js +77 -0
- package/pi-extension/dist/host/config.js +374 -0
- package/pi-extension/dist/host/projection-tags.js +60 -0
- package/pi-extension/dist/host/prompts.js +82 -0
- package/pi-extension/dist/{session-sources → host}/read-only-session.js +112 -1
- package/pi-extension/dist/{runtime → host}/request-history.js +121 -39
- package/pi-extension/dist/host/runtime-state.js +189 -0
- package/pi-extension/dist/{settings/settings-ui.js → host/settings/freeflow-command.js} +620 -410
- package/pi-extension/dist/{settings/settings-tui.js → host/settings/settings-view.js} +186 -73
- package/pi-extension/dist/host/staging.js +28 -0
- package/pi-extension/dist/host/status.js +101 -0
- package/pi-extension/dist/index.js +423 -173
- package/pi-extension/dist/provider-support/cache/anchor.js +21 -0
- package/pi-extension/dist/provider-support/cache/anthropic.js +102 -0
- package/pi-extension/dist/provider-support/cache/health.js +73 -0
- package/pi-extension/dist/provider-support/cache/index.js +58 -0
- package/pi-extension/dist/provider-support/cache/keepalive.js +210 -0
- package/pi-extension/dist/provider-support/cache/monitor.js +17 -0
- package/pi-extension/dist/provider-support/cache/openai-responses.js +48 -0
- package/pi-extension/dist/provider-support/effort.js +13 -0
- package/pi-extension/dist/provider-support/index.js +44 -3
- package/pi-extension/dist/provider-support/{astra → openai}/adapter.js +46 -29
- package/pi-extension/dist/provider-support/{astra → openai}/history.js +10 -8
- package/pi-extension/dist/provider-support/{astra → openai}/index.js +8 -4
- package/pi-extension/dist/provider-support/{astra → openai}/session-state.js +29 -38
- package/pi-extension/dist/provider-support/routes.js +17 -0
- package/pi-extension/dist/tool-execution/apply-patch/grammar.js +23 -0
- package/pi-extension/dist/tool-execution/apply-patch/match.js +38 -0
- package/pi-extension/dist/tool-execution/apply-patch/parser.js +177 -0
- package/pi-extension/dist/tool-execution/apply-patch/plan.js +201 -0
- package/pi-extension/dist/tool-execution/apply-patch/tool.js +84 -0
- package/pi-extension/dist/tool-execution/background.js +238 -0
- package/pi-extension/dist/tool-execution/bash-guard.js +23 -0
- package/pi-extension/dist/tool-execution/config.js +22 -0
- package/pi-extension/dist/tool-execution/edit-messages.js +120 -0
- package/pi-extension/dist/tool-execution/file-state.js +138 -0
- package/pi-extension/dist/tool-execution/file-tracking.js +143 -0
- package/pi-extension/dist/tool-execution/prompt.js +54 -0
- package/pi-extension/dist/tool-execution/tools.js +16 -0
- package/pi-extension/freeflow/README.md +9 -0
- package/plugin.json +1 -1
- package/runtime/prompts/cognitive-routing.md +4 -4
- package/runtime/prompts/core.md +22 -75
- package/runtime/prompts/interaction-contract.md +15 -8
- package/runtime/prompts/tool-execution.md +38 -0
- package/runtime/prompts/working-method.md +108 -0
- package/skills/action-selection/SKILL.md +39 -47
- package/skills/action-selection/references/trajectory-stalls.md +2 -0
- package/skills/bypass/SKILL.md +2 -2
- package/skills/commit-work/SKILL.md +31 -83
- package/skills/commit-work/references/git-state-edges.md +1 -1
- package/skills/commit-work/references/simple-push.md +2 -0
- package/skills/commit-work/references/staging-decisions.md +1 -1
- package/skills/decision-gate/SKILL.md +31 -50
- package/skills/design-for-depth/SKILL.md +44 -53
- package/skills/design-for-depth/references/design-pressure-signals.md +46 -144
- package/skills/design-for-depth/references/implementation-decisions.md +1 -1
- package/skills/design-for-depth/references/interface-design-loop.md +5 -36
- package/skills/design-for-depth/references/module-and-dependency-design.md +2 -32
- package/skills/design-for-depth/references/state-and-failure-boundaries.md +2 -13
- package/skills/diagnose-failure/SKILL.md +39 -24
- package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +2 -0
- package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +2 -2
- package/skills/discuss/SKILL.md +35 -11
- package/skills/discuss/references/discussion-continuity.md +7 -27
- package/skills/evaluate-skill/SKILL.md +49 -80
- package/skills/evaluate-skill/references/evaluation-definition-schema.md +35 -5
- package/skills/evaluate-skill/references/evaluation-design.md +14 -14
- package/skills/evaluate-skill/references/execution-and-evidence.md +26 -5
- package/skills/evaluate-skill/references/review-and-revision.md +3 -1
- package/skills/evaluate-skill/scripts/lib/definitions.mjs +49 -8
- package/skills/evaluate-skill/scripts/lib/pi.mjs +14 -7
- package/skills/evaluate-skill/scripts/lib/review.mjs +196 -0
- package/skills/evaluate-skill/scripts/lib/runner.mjs +45 -8
- package/skills/evaluate-skill/scripts/lib/sandbox.mjs +2 -0
- package/skills/evaluate-skill/scripts/lib/trials.mjs +103 -0
- package/skills/evaluate-skill/scripts/lib/view.mjs +67 -1
- package/skills/evaluate-skill/scripts/pi-command-tool.mjs +144 -0
- package/skills/evaluate-skill/scripts/pi-guard.mjs +30 -13
- package/skills/evaluate-skill/scripts/skill-eval.mjs +114 -34
- package/skills/execute-work/SKILL.md +41 -9
- package/skills/execute-work/references/code-practices.md +2 -0
- package/skills/execute-work/references/execute-work-edges.md +11 -1
- package/skills/handoff/SKILL.md +1 -1
- package/skills/handoff/references/handoff-templates.md +1 -1
- package/skills/review-artifact/SKILL.md +42 -41
- package/skills/review-artifact/references/adjudicate-artifact-review.md +1 -1
- package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +1 -1
- package/skills/review-work/SKILL.md +38 -32
- package/skills/review-work/references/adjudicate-work-review.md +1 -1
- package/skills/review-work/references/independent-work-reviewer-contract.md +1 -1
- package/skills/review-work/references/security-risk-lens.md +2 -4
- package/skills/setup-freeflow/SKILL.md +3 -1
- package/skills/setup-freeflow/references/activation-contract.md +7 -13
- package/skills/setup-freeflow/references/host-setup.md +3 -3
- package/skills/track-work/SKILL.md +113 -161
- package/skills/track-work/references/working-record-format.md +189 -350
- package/skills/track-work/scripts/lib/document.mjs +62 -6
- package/skills/track-work/scripts/lib/format.mjs +16 -1
- package/skills/track-work/scripts/lib/store.mjs +9 -8
- package/skills/track-work/scripts/lib/transitions.mjs +79 -4
- package/skills/track-work/scripts/working-record.mjs +34 -12
- package/skills/verify-work/SKILL.md +29 -20
- package/skills/verify-work/references/browser-runtime-evidence.md +1 -5
- package/skills/verify-work/references/integration-evidence.md +2 -0
- package/skills/verify-work/references/performance-evidence.md +3 -1
- package/skills/verify-work/references/test-design.md +3 -1
- package/skills/workflow/SKILL.md +65 -21
- package/skills/workflow/references/domain-skill-composition.md +5 -3
- package/skills/write-docs/SKILL.md +83 -0
- package/skills/write-docs/references/documentation-shapes.md +119 -0
- package/skills/write-docs/scripts/docs-check.mjs +139 -0
- package/skills/write-plan/SKILL.md +40 -40
- package/skills/write-plan/references/plan-shapes.md +1 -1
- package/skills/write-skill/SKILL.md +107 -147
- package/skills/write-skill/references/activation-boundaries.md +2 -0
- package/skills/write-skill/references/agent-first-instructions.md +22 -10
- package/skills/write-skill/references/progressive-disclosure.md +4 -2
- package/skills/write-skill/references/skill-dependency-graphs.md +2 -0
- package/skills/write-skill/scripts/lib/similarity.mjs +56 -0
- package/skills/write-skill/scripts/skill-author.mjs +18 -2
- package/skills/write-spec/SKILL.md +34 -41
- package/skills/write-spec/references/artifact-standards.md +1 -1
- package/skills/write-spec/references/decision-records.md +2 -0
- package/skills/write-spec/references/spec-shapes.md +1 -1
- package/capabilities/context-virtualization/SKILL.md +0 -103
- package/capabilities/conversation-history/SKILL.md +0 -53
- package/hooks/tests/check-host-adapters.test.mjs +0 -82
- package/hooks/tests/check-runtime-context-hook.sh +0 -212
- package/pi-extension/dist/cognitive-routing-v2/runtime.js +0 -2181
- package/pi-extension/dist/context-virtualization/commands.js +0 -89
- package/pi-extension/dist/context-virtualization/projector.js +0 -42
- package/pi-extension/dist/context-virtualization/runtime.js +0 -378
- package/pi-extension/dist/context-virtualization/state.js +0 -57
- package/pi-extension/dist/context-virtualization/types.js +0 -10
- package/pi-extension/dist/conversation-history/passages.js +0 -66
- package/pi-extension/dist/conversation-history/ranking.js +0 -105
- package/pi-extension/dist/conversation-history/runtime.js +0 -226
- package/pi-extension/dist/conversation-history/search.js +0 -56
- package/pi-extension/dist/conversation-history/types.js +0 -1
- package/pi-extension/dist/freeflow-context/resolver.js +0 -120
- package/pi-extension/dist/freeflow-context/runtime.js +0 -80
- package/pi-extension/dist/freeflow-context/source-projector.js +0 -95
- package/pi-extension/dist/freeflow-context/tool.js +0 -314
- package/pi-extension/dist/freeflow-context/types.js +0 -12
- package/pi-extension/dist/freeflow-context/visibility.js +0 -28
- package/pi-extension/dist/integrations/mcp-config.js +0 -29
- package/pi-extension/dist/runtime/runtime-context.js +0 -754
- package/pi-extension/dist/runtime/runtime-identity.js +0 -5
- package/pi-extension/dist/session-sources/provenance.js +0 -32
- package/pi-extension/dist/ui/text-component.js +0 -57
- package/pi-extension/src/cognitive-routing-v2/budget.ts +0 -42
- package/pi-extension/src/cognitive-routing-v2/config.ts +0 -173
- package/pi-extension/src/cognitive-routing-v2/projection.ts +0 -375
- package/pi-extension/src/cognitive-routing-v2/render.ts +0 -277
- package/pi-extension/src/cognitive-routing-v2/runtime.ts +0 -2241
- package/pi-extension/src/cognitive-routing-v2/schemas.ts +0 -180
- package/pi-extension/src/cognitive-routing-v2/state.ts +0 -839
- package/pi-extension/src/cognitive-routing-v2/tools.ts +0 -62
- package/pi-extension/src/cognitive-routing-v2/types.ts +0 -254
- package/pi-extension/src/context-virtualization/commands.ts +0 -111
- package/pi-extension/src/context-virtualization/projector.ts +0 -62
- package/pi-extension/src/context-virtualization/runtime.ts +0 -428
- package/pi-extension/src/context-virtualization/state.ts +0 -79
- package/pi-extension/src/context-virtualization/types.ts +0 -92
- package/pi-extension/src/conversation-history/passages.ts +0 -81
- package/pi-extension/src/conversation-history/ranking.ts +0 -155
- package/pi-extension/src/conversation-history/runtime.ts +0 -250
- package/pi-extension/src/conversation-history/search.ts +0 -79
- package/pi-extension/src/conversation-history/types.ts +0 -31
- package/pi-extension/src/freeflow-context/resolver.ts +0 -139
- package/pi-extension/src/freeflow-context/runtime.ts +0 -100
- package/pi-extension/src/freeflow-context/source-projector.ts +0 -106
- package/pi-extension/src/freeflow-context/tool.ts +0 -366
- package/pi-extension/src/freeflow-context/types.ts +0 -70
- package/pi-extension/src/freeflow-context/visibility.ts +0 -53
- package/pi-extension/src/index.ts +0 -390
- package/pi-extension/src/integrations/mcp-config.ts +0 -32
- package/pi-extension/src/provider-support/astra/adapter.ts +0 -108
- package/pi-extension/src/provider-support/astra/history.ts +0 -145
- package/pi-extension/src/provider-support/astra/index.ts +0 -12
- package/pi-extension/src/provider-support/astra/session-state.ts +0 -113
- package/pi-extension/src/provider-support/index.ts +0 -5
- package/pi-extension/src/runtime/request-history.ts +0 -243
- package/pi-extension/src/runtime/runtime-context.ts +0 -860
- package/pi-extension/src/runtime/runtime-identity.ts +0 -8
- package/pi-extension/src/session-sources/events.ts +0 -230
- package/pi-extension/src/session-sources/provenance.ts +0 -39
- package/pi-extension/src/session-sources/read-only-session.ts +0 -170
- package/pi-extension/src/session-sources/sources.ts +0 -334
- package/pi-extension/src/settings/settings-tui.ts +0 -501
- package/pi-extension/src/settings/settings-ui.ts +0 -1652
- package/pi-extension/src/ui/text-component.ts +0 -66
- package/pi-extension/tests/cache-reuse/history.test.js +0 -117
- package/pi-extension/tests/cache-reuse/native.test.js +0 -91
- package/pi-extension/tests/cache-reuse/settings.test.js +0 -211
- package/pi-extension/tests/cognitive-routing-v2/communication.test.js +0 -577
- package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +0 -183
- package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +0 -557
- package/pi-extension/tests/cognitive-routing-v2/native.test.js +0 -474
- package/pi-extension/tests/cognitive-routing-v2/performance.test.js +0 -87
- package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +0 -300
- package/pi-extension/tests/cognitive-routing-v2/projection.test.js +0 -241
- package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +0 -1034
- package/pi-extension/tests/cognitive-routing-v2/render.test.js +0 -244
- package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +0 -423
- package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +0 -521
- package/pi-extension/tests/cognitive-routing-v2/state.test.js +0 -586
- package/pi-extension/tests/context-virtualization/extension.test.js +0 -775
- package/pi-extension/tests/context-virtualization/projector.test.js +0 -110
- package/pi-extension/tests/context-virtualization/runtime.test.js +0 -230
- package/pi-extension/tests/conversation-history/runtime.test.js +0 -98
- package/pi-extension/tests/conversation-history/search.test.js +0 -97
- package/pi-extension/tests/fixtures/pi-host.js +0 -4
- package/pi-extension/tests/fixtures/routing-native.js +0 -191
- package/pi-extension/tests/integration/mode-free-core.test.js +0 -220
- package/pi-extension/tests/integration/pi-extension.test.js +0 -1447
- package/pi-extension/tests/integration/prompt-architecture.test.js +0 -447
- package/pi-extension/tests/integration/unaffected-regressions.test.js +0 -478
- package/pi-extension/tests/provider-support/astra-boundaries.test.js +0 -75
- package/pi-extension/tests/provider-support/astra.test.js +0 -306
- package/pi-extension/tests/provider-support/cli.test.js +0 -92
- package/pi-extension/tests/provider-support/native.test.js +0 -183
- package/pi-extension/tests/settings/settings-tui.test.js +0 -243
- package/pi-extension/tsconfig.json +0 -16
- package/runtime/prompts/context-virtualization.md +0 -3
- package/runtime/prompts/conversation-history.md +0 -3
- package/skills/design-for-depth/references/software-design-philosophy.md +0 -202
- /package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/budget.js +0 -0
- /package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/render.js +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Messages Freeflow gives around Pi's edit and write. Every error ends in one literal next action; results carry no
|
|
2
|
+
// reassurance. The wording was reviewed against the Beyond the Weights findings (Task 006 evidence).
|
|
3
|
+
const MAX_SHOWN_LINES = 20;
|
|
4
|
+
const MAX_LINE_CHARS = 300;
|
|
5
|
+
const MIN_SIMILARITY = 0.5;
|
|
6
|
+
/** Pi's edit fuzzy normalization (core/tools/edit-diff.ts normalizeForFuzzyMatch, not exported). */
|
|
7
|
+
export function normalizeForMatch(text) {
|
|
8
|
+
return text
|
|
9
|
+
.normalize("NFKC")
|
|
10
|
+
.split("\n")
|
|
11
|
+
.map((line) => line.trimEnd())
|
|
12
|
+
.join("\n")
|
|
13
|
+
.replace(/[‘’‚‛]/g, "'")
|
|
14
|
+
.replace(/[“”„‟]/g, '"')
|
|
15
|
+
.replace(/[‐‑‒–—―−]/g, "-")
|
|
16
|
+
.replace(/[ - ]/g, " ");
|
|
17
|
+
}
|
|
18
|
+
/** File text as Pi's edit matches it: without a BOM and with LF line endings. */
|
|
19
|
+
export function editableText(raw) {
|
|
20
|
+
return raw.replace(/^/, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
21
|
+
}
|
|
22
|
+
function bigrams(text) {
|
|
23
|
+
const counts = new Map();
|
|
24
|
+
for (let i = 0; i < text.length - 1; i++) {
|
|
25
|
+
const pair = text.slice(i, i + 2);
|
|
26
|
+
counts.set(pair, (counts.get(pair) ?? 0) + 1);
|
|
27
|
+
}
|
|
28
|
+
return counts;
|
|
29
|
+
}
|
|
30
|
+
function lineSimilarity(a, b) {
|
|
31
|
+
const x = a.trim(),
|
|
32
|
+
y = b.trim();
|
|
33
|
+
if (x === y) return 1;
|
|
34
|
+
if (x.length < 2 || y.length < 2) return 0;
|
|
35
|
+
const left = bigrams(x),
|
|
36
|
+
right = bigrams(y);
|
|
37
|
+
let shared = 0;
|
|
38
|
+
for (const [pair, count] of left) shared += Math.min(count, right.get(pair) ?? 0);
|
|
39
|
+
return (2 * shared) / (x.length - 1 + (y.length - 1));
|
|
40
|
+
}
|
|
41
|
+
/** The window of the file most similar to the old text, as 1-based inclusive lines, or undefined when none is close. */
|
|
42
|
+
export function closestMatch(fileText, oldText) {
|
|
43
|
+
const lines = fileText.split("\n");
|
|
44
|
+
const wanted = oldText.replace(/\n+$/, "").split("\n");
|
|
45
|
+
const size = Math.min(wanted.length, lines.length);
|
|
46
|
+
if (size === 0) return undefined;
|
|
47
|
+
let best;
|
|
48
|
+
for (let start = 0; start + size <= lines.length; start++) {
|
|
49
|
+
let total = 0;
|
|
50
|
+
for (let i = 0; i < size; i++) total += lineSimilarity(lines[start + i], wanted[i]);
|
|
51
|
+
const score = total / wanted.length;
|
|
52
|
+
if (!best || score > best.score) best = { start, score };
|
|
53
|
+
}
|
|
54
|
+
return best && best.score >= MIN_SIMILARITY ? { start: best.start + 1, end: best.start + size } : undefined;
|
|
55
|
+
}
|
|
56
|
+
/** 1-based start lines of every place the old text matches, compared as Pi's edit compares it. */
|
|
57
|
+
export function matchingLines(fileText, oldText) {
|
|
58
|
+
const haystack = normalizeForMatch(fileText),
|
|
59
|
+
needle = normalizeForMatch(oldText);
|
|
60
|
+
const found = [];
|
|
61
|
+
if (!needle) return found;
|
|
62
|
+
for (let index = haystack.indexOf(needle); index >= 0; index = haystack.indexOf(needle, index + 1))
|
|
63
|
+
found.push(haystack.slice(0, index).split("\n").length);
|
|
64
|
+
return found;
|
|
65
|
+
}
|
|
66
|
+
function shownLines(fileText, start, end) {
|
|
67
|
+
return fileText
|
|
68
|
+
.split("\n")
|
|
69
|
+
.slice(start - 1, Math.min(end, start - 1 + MAX_SHOWN_LINES))
|
|
70
|
+
.map((line) => (line.length > MAX_LINE_CHARS ? `${line.slice(0, MAX_LINE_CHARS)}…` : line))
|
|
71
|
+
.join("\n");
|
|
72
|
+
}
|
|
73
|
+
const NOT_FOUND = /^Could not find (?:the exact text|edits\[(\d+)\]) in (.+?)\. The old ?[tT]ext must match exactly/;
|
|
74
|
+
const NOT_UNIQUE = /^Found (\d+) occurrences of (?:the text|edits\[(\d+)\]) in (.+?)\. /;
|
|
75
|
+
/**
|
|
76
|
+
* Replace Pi's not-found or not-unique edit error with one literal next action. Returns undefined when the error is
|
|
77
|
+
* another kind (overlap, empty, no change) or Pi worded it differently, so the result stays exactly as Pi wrote it.
|
|
78
|
+
*/
|
|
79
|
+
export function rewriteEditError(error, path, edits, fileText) {
|
|
80
|
+
const notFound = NOT_FOUND.exec(error);
|
|
81
|
+
if (notFound) {
|
|
82
|
+
const index = notFound[1] === undefined ? 0 : Number(notFound[1]);
|
|
83
|
+
const subject = notFound[1] === undefined ? path : `${path} edits[${index}]`;
|
|
84
|
+
const oldText = editableText(String(edits[index]?.oldText ?? ""));
|
|
85
|
+
const match = closestMatch(fileText, oldText);
|
|
86
|
+
if (!match)
|
|
87
|
+
return `${subject}: old text not found and no similar lines exist in the current file. Read the region you meant, then retry.`;
|
|
88
|
+
return [
|
|
89
|
+
`${subject}: old text not found. Closest match, lines ${match.start}–${match.end} (current file contents):`,
|
|
90
|
+
shownLines(fileText, match.start, match.end),
|
|
91
|
+
"If these are the lines you meant, retry with old text copied exactly from them. If not, read the region you meant, then retry.",
|
|
92
|
+
].join("\n");
|
|
93
|
+
}
|
|
94
|
+
const notUnique = NOT_UNIQUE.exec(error);
|
|
95
|
+
if (notUnique) {
|
|
96
|
+
const index = notUnique[2] === undefined ? 0 : Number(notUnique[2]);
|
|
97
|
+
const subject = notUnique[2] === undefined ? path : `${path} edits[${index}]`;
|
|
98
|
+
const lines = matchingLines(fileText, editableText(String(edits[index]?.oldText ?? "")));
|
|
99
|
+
const places = lines.length > 0 ? lines.length : Number(notUnique[1]);
|
|
100
|
+
const where = lines.length > 0 ? ` (lines ${lines.join(", ")})` : "";
|
|
101
|
+
return `${subject}: old text matches ${places} places${where}. To change one place, add surrounding lines until it matches only that place. To change every place, give one edits[] entry per place, each with enough surrounding lines to match only that place.`;
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
export const writeRefusal = (path) =>
|
|
106
|
+
`${path} exists and has not been read in this session. Read ${path}, then retry the write.`;
|
|
107
|
+
export const changedAfterEdit = (path) => `${path} changed since you read it; the edit applied to the current text.`;
|
|
108
|
+
export const changedAfterWrite = (path) =>
|
|
109
|
+
`${path} changed since you read it; the write replaced the current contents.`;
|
|
110
|
+
export const noNeedToReread = (path) => `No need to re-read ${path} to confirm this edit.`;
|
|
111
|
+
/** Files the model read that changed or disappeared outside its own edits, as a fact and one next action. */
|
|
112
|
+
export function changedFilesNotice(changed, deleted) {
|
|
113
|
+
const lines = [];
|
|
114
|
+
if (changed.length > 0)
|
|
115
|
+
lines.push(
|
|
116
|
+
`Files you read earlier changed since you read them: ${changed.join(", ")}. Read what you need from them again before editing them.`,
|
|
117
|
+
);
|
|
118
|
+
if (deleted.length > 0) lines.push(`Files you read earlier no longer exist: ${deleted.join(", ")}.`);
|
|
119
|
+
return lines.length > 0 ? lines.join("\n") : undefined;
|
|
120
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
3
|
+
import { readFile, stat } from "node:fs/promises";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { isAbsolute, resolve } from "node:path";
|
|
6
|
+
// Files above this size are identified by size and modification time instead of a content hash.
|
|
7
|
+
export const HASH_LIMIT_BYTES = 8 * 1024 * 1024;
|
|
8
|
+
const UNICODE_SPACES = /[ - ]/g;
|
|
9
|
+
/** Resolve a tool path the way Pi's tools do: strip `@`, normalize odd spaces, expand `~`, resolve against cwd. */
|
|
10
|
+
export function resolveToolPath(path, cwd) {
|
|
11
|
+
let candidate = path.replace(UNICODE_SPACES, " ");
|
|
12
|
+
if (candidate.startsWith("@")) candidate = candidate.slice(1);
|
|
13
|
+
if (candidate === "~") candidate = homedir();
|
|
14
|
+
else if (candidate.startsWith("~/")) candidate = `${homedir()}${candidate.slice(1)}`;
|
|
15
|
+
const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(cwd, candidate);
|
|
16
|
+
if (existsSync(resolved)) return resolved;
|
|
17
|
+
// Pi's read also finds macOS names typed differently: narrow space before AM/PM, NFD, curly apostrophe.
|
|
18
|
+
for (const variant of [
|
|
19
|
+
resolved.replace(/ (AM|PM)\./gi, " $1."),
|
|
20
|
+
resolved.normalize("NFD"),
|
|
21
|
+
resolved.replace(/'/g, "’"),
|
|
22
|
+
])
|
|
23
|
+
if (variant !== resolved && existsSync(variant)) return variant;
|
|
24
|
+
return resolved;
|
|
25
|
+
}
|
|
26
|
+
function fileKey(path, cwd) {
|
|
27
|
+
const resolved = resolveToolPath(path, cwd);
|
|
28
|
+
try {
|
|
29
|
+
return realpathSync(resolved);
|
|
30
|
+
} catch {
|
|
31
|
+
return resolved;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function fingerprint(file) {
|
|
35
|
+
let info;
|
|
36
|
+
try {
|
|
37
|
+
info = await stat(file);
|
|
38
|
+
} catch {
|
|
39
|
+
return "absent";
|
|
40
|
+
}
|
|
41
|
+
if (!info.isFile()) return "absent";
|
|
42
|
+
if (info.size > HASH_LIMIT_BYTES) return { size: info.size, mtimeMs: info.mtimeMs };
|
|
43
|
+
const bytes = await readFile(file);
|
|
44
|
+
return { sha256: createHash("sha256").update(bytes).digest("hex"), size: bytes.length };
|
|
45
|
+
}
|
|
46
|
+
async function statOf(file) {
|
|
47
|
+
try {
|
|
48
|
+
const info = await stat(file);
|
|
49
|
+
return info.isFile() ? { size: info.size, mtimeMs: info.mtimeMs } : "absent";
|
|
50
|
+
} catch {
|
|
51
|
+
return "absent";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const sameStat = (a, b) =>
|
|
55
|
+
a !== undefined && (a === "absent" || b === "absent" ? a === b : a.size === b.size && a.mtimeMs === b.mtimeMs);
|
|
56
|
+
function sameFingerprint(a, b) {
|
|
57
|
+
if (a === "absent" || b === "absent") return a === b;
|
|
58
|
+
if ("sha256" in a && "sha256" in b) return a.sha256 === b.sha256;
|
|
59
|
+
if ("mtimeMs" in a && "mtimeMs" in b) return a.size === b.size && a.mtimeMs === b.mtimeMs;
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
/** The files a successful tool call read or changed, from a tool call's name and arguments. */
|
|
63
|
+
export function touchedPaths(toolName, input) {
|
|
64
|
+
if (["read", "edit", "write"].includes(toolName)) return typeof input?.path === "string" ? [input.path] : [];
|
|
65
|
+
if (toolName === "apply_patch" && typeof input?.input === "string")
|
|
66
|
+
return [...input.input.matchAll(/^\s*\*\*\* (?:Add File|Update File|Delete File|Move to): (.+?)\s*$/gm)].map(
|
|
67
|
+
(match) => match[1],
|
|
68
|
+
);
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Which files the model has read or written on the current branch, and their content at that moment. Held in memory
|
|
73
|
+
* and rebuilt from the branch after navigation or resume; never persisted and never consulted on the request path.
|
|
74
|
+
*/
|
|
75
|
+
export class FileState {
|
|
76
|
+
files = new Map();
|
|
77
|
+
async observe(path, cwd, via) {
|
|
78
|
+
const key = fileKey(path, cwd);
|
|
79
|
+
const at = await statOf(key);
|
|
80
|
+
this.files.set(key, { fingerprint: await fingerprint(key), via, stat: at });
|
|
81
|
+
}
|
|
82
|
+
wasRead(path, cwd) {
|
|
83
|
+
return this.files.has(fileKey(path, cwd));
|
|
84
|
+
}
|
|
85
|
+
/** Whether the file differs from its last observation; false when it was never observed. */
|
|
86
|
+
async changedSinceObserved(path, cwd) {
|
|
87
|
+
const key = fileKey(path, cwd);
|
|
88
|
+
const known = this.files.get(key);
|
|
89
|
+
if (!known) return false;
|
|
90
|
+
return !sameFingerprint(known.fingerprint, await fingerprint(key));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Observed files whose content changed since the model last read or wrote them, each reported once per change.
|
|
94
|
+
* Costs one stat per observed file; a file is hashed only when its size or modification time moved.
|
|
95
|
+
*/
|
|
96
|
+
async changedSinceNoticed() {
|
|
97
|
+
const changed = [];
|
|
98
|
+
for (const [key, known] of this.files) {
|
|
99
|
+
const at = await statOf(key);
|
|
100
|
+
if (sameStat(known.stat, at)) continue;
|
|
101
|
+
known.stat = at;
|
|
102
|
+
const now = await fingerprint(key);
|
|
103
|
+
if (sameFingerprint(known.fingerprint, now)) continue;
|
|
104
|
+
if (known.noticed !== undefined && sameFingerprint(known.noticed, now)) continue;
|
|
105
|
+
known.noticed = now;
|
|
106
|
+
changed.push({ path: key, deleted: now === "absent" });
|
|
107
|
+
}
|
|
108
|
+
return changed;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Mark every file a successful read, edit, write or patch on the branch touched, with today's content. A Freeflow
|
|
112
|
+
* compaction starts over: the model then holds only the files it carried.
|
|
113
|
+
*/
|
|
114
|
+
async rebuild(branch, cwd) {
|
|
115
|
+
this.files.clear();
|
|
116
|
+
const calls = new Map();
|
|
117
|
+
const touched = new Set();
|
|
118
|
+
for (const entry of branch) {
|
|
119
|
+
if (entry?.type === "compaction" && entry.details?.freeflow) {
|
|
120
|
+
touched.clear();
|
|
121
|
+
for (const item of entry.details.freeflow.carried ?? [])
|
|
122
|
+
if (item?.kind === "file" && typeof item.path === "string" && !item.error) touched.add(item.path);
|
|
123
|
+
}
|
|
124
|
+
const message = entry?.type === "message" ? entry.message : undefined;
|
|
125
|
+
if (message?.role === "assistant")
|
|
126
|
+
for (const block of message.content ?? [])
|
|
127
|
+
if (block?.type === "toolCall") calls.set(block.id, { name: block.name, input: block.arguments });
|
|
128
|
+
if (message?.role === "toolResult" && message.isError !== true) {
|
|
129
|
+
const call = calls.get(message.toolCallId);
|
|
130
|
+
if (call) for (const path of touchedPaths(call.name, call.input)) touched.add(path);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
for (const path of touched) await this.observe(path, cwd, "rebuilt");
|
|
134
|
+
}
|
|
135
|
+
reset() {
|
|
136
|
+
this.files.clear();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, relative } from "node:path";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { FileState, resolveToolPath } from "./file-state.js";
|
|
5
|
+
import {
|
|
6
|
+
changedAfterEdit,
|
|
7
|
+
changedAfterWrite,
|
|
8
|
+
changedFilesNotice,
|
|
9
|
+
editableText,
|
|
10
|
+
noNeedToReread,
|
|
11
|
+
rewriteEditError,
|
|
12
|
+
writeRefusal,
|
|
13
|
+
} from "./edit-messages.js";
|
|
14
|
+
import { cachedBranch } from "../host/branch.js";
|
|
15
|
+
function existingFile(path, cwd) {
|
|
16
|
+
try {
|
|
17
|
+
return statSync(resolveToolPath(path, cwd)).isFile();
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Tools whose own targets are observed after they run; any other tool may change files behind the model's back.
|
|
23
|
+
const TRACKED_TOOLS = ["read", "edit", "write", "apply_patch"];
|
|
24
|
+
/** A tracked file (keyed by real path) as the model would name it: relative to the working directory when inside. */
|
|
25
|
+
function shown(path, cwd) {
|
|
26
|
+
let real = cwd;
|
|
27
|
+
try {
|
|
28
|
+
real = realpathSync(cwd);
|
|
29
|
+
} catch {}
|
|
30
|
+
for (const base of [real, cwd]) {
|
|
31
|
+
const inside = relative(base, path);
|
|
32
|
+
if (inside && !inside.startsWith("..") && !isAbsolute(inside)) return inside;
|
|
33
|
+
}
|
|
34
|
+
return path;
|
|
35
|
+
}
|
|
36
|
+
function withLines(content, lines) {
|
|
37
|
+
const text = content.map((part) => (part?.type === "text" ? part.text : "")).join("");
|
|
38
|
+
return [
|
|
39
|
+
{ type: "text", text: [text, ...lines].filter(Boolean).join("\n") },
|
|
40
|
+
...content.filter((part) => part?.type !== "text"),
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Claude Code's file awareness around Pi's read, edit and write: refuse a whole-file write over a file never read,
|
|
45
|
+
* say when an edited file changed since it was read, and turn edit errors into one next action. Nested calls
|
|
46
|
+
* (codemode) are handled like direct ones.
|
|
47
|
+
*/
|
|
48
|
+
export class FileTracking {
|
|
49
|
+
state = new FileState();
|
|
50
|
+
changed = new Map();
|
|
51
|
+
built = false;
|
|
52
|
+
reset() {
|
|
53
|
+
this.state.reset();
|
|
54
|
+
this.changed.clear();
|
|
55
|
+
this.built = false;
|
|
56
|
+
}
|
|
57
|
+
/** Forget the branch's files; they are rebuilt from the new branch before the next tool call. */
|
|
58
|
+
invalidate() {
|
|
59
|
+
this.built = false;
|
|
60
|
+
}
|
|
61
|
+
clearPending() {
|
|
62
|
+
this.changed.clear();
|
|
63
|
+
}
|
|
64
|
+
/** After a Freeflow compaction the model holds only the carried files, read at compaction time. */
|
|
65
|
+
async compacted(carried, ctx) {
|
|
66
|
+
this.state.reset();
|
|
67
|
+
this.changed.clear();
|
|
68
|
+
for (const path of carried) await this.state.observe(path, ctx.cwd, "read");
|
|
69
|
+
this.built = true;
|
|
70
|
+
}
|
|
71
|
+
/** The branch's file state for apply_patch, built when needed. */
|
|
72
|
+
async forPatch(ctx) {
|
|
73
|
+
await this.ensureBuilt(ctx);
|
|
74
|
+
return this.state;
|
|
75
|
+
}
|
|
76
|
+
/** Record files a successful apply_patch wrote or moved, as a successful write records them. */
|
|
77
|
+
async written(paths, ctx) {
|
|
78
|
+
await this.ensureBuilt(ctx);
|
|
79
|
+
for (const path of paths) await this.state.observe(path, ctx.cwd, "apply_patch");
|
|
80
|
+
}
|
|
81
|
+
async ensureBuilt(ctx) {
|
|
82
|
+
if (this.built) return;
|
|
83
|
+
await this.state.rebuild(cachedBranch(ctx.sessionManager) ?? [], ctx.cwd);
|
|
84
|
+
this.built = true;
|
|
85
|
+
}
|
|
86
|
+
async toolCall(event, ctx) {
|
|
87
|
+
const name = event.toolName;
|
|
88
|
+
const path = event.input?.path;
|
|
89
|
+
if (!["edit", "write"].includes(name) || typeof path !== "string") return undefined;
|
|
90
|
+
await this.ensureBuilt(ctx);
|
|
91
|
+
if (name === "write" && existingFile(path, ctx.cwd) && !this.state.wasRead(path, ctx.cwd))
|
|
92
|
+
return { block: true, reason: writeRefusal(path) };
|
|
93
|
+
if (await this.state.changedSinceObserved(path, ctx.cwd)) this.changed.set(event.toolCallId, path);
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
/** Files the model read that changed since, not yet reported; undefined when there are none. */
|
|
97
|
+
async changedNotice(ctx, build = true) {
|
|
98
|
+
// Before a prompt nothing is rebuilt: a branch not yet rebuilt has no baseline to compare against.
|
|
99
|
+
if (!build && !this.built) return undefined;
|
|
100
|
+
await this.ensureBuilt(ctx);
|
|
101
|
+
const found = await this.state.changedSinceNoticed();
|
|
102
|
+
return changedFilesNotice(
|
|
103
|
+
found.filter((file) => !file.deleted).map((file) => shown(file.path, ctx.cwd)),
|
|
104
|
+
found.filter((file) => file.deleted).map((file) => shown(file.path, ctx.cwd)),
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
async toolResult(event, ctx) {
|
|
108
|
+
const name = event.toolName;
|
|
109
|
+
// A command (or any other tool) may have changed files the model read. Nested calls are reported on the
|
|
110
|
+
// result of the script that made them, which is what the model sees.
|
|
111
|
+
if (!TRACKED_TOOLS.includes(name)) {
|
|
112
|
+
if (event.parentToolCallId) return undefined;
|
|
113
|
+
const notice = await this.changedNotice(ctx);
|
|
114
|
+
return notice === undefined ? undefined : { content: withLines(event.content ?? [], [notice]) };
|
|
115
|
+
}
|
|
116
|
+
const path = event.input?.path;
|
|
117
|
+
if (!["read", "edit", "write"].includes(name) || typeof path !== "string") return undefined;
|
|
118
|
+
await this.ensureBuilt(ctx);
|
|
119
|
+
const changed = this.changed.get(event.toolCallId);
|
|
120
|
+
this.changed.delete(event.toolCallId);
|
|
121
|
+
if (event.isError) {
|
|
122
|
+
if (name !== "edit") return undefined;
|
|
123
|
+
let fileText;
|
|
124
|
+
try {
|
|
125
|
+
fileText = editableText(await readFile(resolveToolPath(path, ctx.cwd), "utf8"));
|
|
126
|
+
} catch {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
const error = (event.content ?? []).map((part) => (part?.type === "text" ? part.text : "")).join("");
|
|
130
|
+
const rewritten = rewriteEditError(error, path, event.input?.edits ?? [], fileText);
|
|
131
|
+
return rewritten === undefined ? undefined : { content: [{ type: "text", text: rewritten }] };
|
|
132
|
+
}
|
|
133
|
+
await this.state.observe(path, ctx.cwd, name);
|
|
134
|
+
if (name === "read") return undefined;
|
|
135
|
+
const lines =
|
|
136
|
+
name === "edit"
|
|
137
|
+
? [...(changed ? [changedAfterEdit(path)] : []), noNeedToReread(path)]
|
|
138
|
+
: changed
|
|
139
|
+
? [changedAfterWrite(path)]
|
|
140
|
+
: [];
|
|
141
|
+
return lines.length > 0 ? { content: withLines(event.content ?? [], lines) } : undefined;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { homedir, platform, release } from "node:os";
|
|
2
|
+
import { getShellConfig } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
// Pi's generic rule lines that contradict the taught bash-first working style or repeat Freeflow's interaction
|
|
4
|
+
// contract. Every other rule, including each tool's and extension's guidelines, stays as Pi renders it.
|
|
5
|
+
const REMOVED_RULES = new Set([
|
|
6
|
+
"- Use read to examine files instead of cat or sed.",
|
|
7
|
+
"- Be concise in your responses",
|
|
8
|
+
"- Show file paths clearly when working with files",
|
|
9
|
+
]);
|
|
10
|
+
function renderedSection(systemPrompt, name) {
|
|
11
|
+
const match = new RegExp(`<${name}>\\n([\\s\\S]*?)\\n</${name}>`).exec(systemPrompt);
|
|
12
|
+
return match?.[1];
|
|
13
|
+
}
|
|
14
|
+
function rules(systemPrompt) {
|
|
15
|
+
const rendered = renderedSection(systemPrompt, "rules");
|
|
16
|
+
if (rendered === undefined) return undefined;
|
|
17
|
+
const lines = rendered.split("\n");
|
|
18
|
+
const kept = lines.filter((line) => !REMOVED_RULES.has(line));
|
|
19
|
+
return kept.length === lines.length || kept.length === 0 ? undefined : kept.join("\n");
|
|
20
|
+
}
|
|
21
|
+
function docs(systemPrompt) {
|
|
22
|
+
const rendered = renderedSection(systemPrompt, "docs");
|
|
23
|
+
if (rendered === undefined) return undefined;
|
|
24
|
+
const path = (label) => new RegExp(`^- ${label}: (.+?)(?: \\(|$)`, "m").exec(rendered)?.[1];
|
|
25
|
+
const [readme, docsPath, examples] = [path("Main documentation"), path("Additional docs"), path("Examples")];
|
|
26
|
+
if (!readme || !docsPath || !examples) return undefined;
|
|
27
|
+
return `Pi documentation (read only when the user asks about pi itself): ${readme}, ${docsPath}, ${examples}.`;
|
|
28
|
+
}
|
|
29
|
+
function environment(shellPath) {
|
|
30
|
+
let shell;
|
|
31
|
+
try {
|
|
32
|
+
shell = getShellConfig(shellPath?.startsWith("~/") ? `${homedir()}${shellPath.slice(1)}` : shellPath).shell;
|
|
33
|
+
} catch {
|
|
34
|
+
shell = "unavailable";
|
|
35
|
+
}
|
|
36
|
+
return `Platform: ${platform()} ${release()}. Shell for bash: ${shell}.`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Pi prompt sections Freeflow owns while Tool Execution is effective. Every value derives from inputs fixed for the
|
|
40
|
+
* configuration and machine, so the system prompt stays identical across requests and sessions. Pi's preamble is
|
|
41
|
+
* never touched: the Claude subscription plugin shapes it only when Pi's own preamble is present.
|
|
42
|
+
*/
|
|
43
|
+
export function toolExecutionSections(systemPrompt, shellPath) {
|
|
44
|
+
const sections = {};
|
|
45
|
+
const ownRules = rules(systemPrompt);
|
|
46
|
+
if (ownRules !== undefined) sections.rules = ownRules;
|
|
47
|
+
const ownDocs = docs(systemPrompt);
|
|
48
|
+
if (ownDocs !== undefined) sections.docs = ownDocs;
|
|
49
|
+
return sections;
|
|
50
|
+
}
|
|
51
|
+
/** Sections Tool Execution adds after Freeflow's guidance: the environment facts, then its guidance when available. */
|
|
52
|
+
export function toolExecutionTail(shellPath, guidance) {
|
|
53
|
+
return { environment: environment(shellPath), ...(guidance ? { tool_execution: guidance } : {}) };
|
|
54
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { APPLY_PATCH } from "./apply-patch/tool.js";
|
|
2
|
+
import { BASH_BACKGROUND, STOP_BACKGROUND } from "./background.js";
|
|
3
|
+
/** Freeflow's own Tool Execution tools; registered once and declared exactly while Tool Execution is effective. */
|
|
4
|
+
export const TOOL_EXECUTION_TOOLS = [APPLY_PATCH, BASH_BACKGROUND, STOP_BACKGROUND];
|
|
5
|
+
/**
|
|
6
|
+
* Declare or withdraw Freeflow's tools when Tool Execution's effective state changes. Missing tools are appended and
|
|
7
|
+
* existing ones keep their place, so repeated calls leave the tool list unchanged within a configuration.
|
|
8
|
+
*/
|
|
9
|
+
export function applyToolExecutionTools(pi, effective) {
|
|
10
|
+
if (typeof pi.getActiveTools !== "function" || typeof pi.setActiveTools !== "function") return;
|
|
11
|
+
const current = pi.getActiveTools();
|
|
12
|
+
const next = effective
|
|
13
|
+
? [...current, ...TOOL_EXECUTION_TOOLS.filter((name) => !current.includes(name))]
|
|
14
|
+
: current.filter((name) => !TOOL_EXECUTION_TOOLS.includes(name));
|
|
15
|
+
if (next.length !== current.length) pi.setActiveTools(next);
|
|
16
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Extension entry point
|
|
2
|
+
|
|
3
|
+
`index.js` is the file Pi loads: the root `package.json` lists it under `pi.extensions`. It only re-exports the compiled extension:
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
export { default } from "../dist/index.js";
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Keeping this stable path separate from `dist/` lets the build delete and recreate `dist/` without changing what the package manifest points to. Do not add logic here; the extension starts in [`src/index.ts`](../src/index.ts).
|
package/plugin.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
## Cognitive Routing Cue
|
|
2
2
|
|
|
3
|
-
Cognitive Routing places work across configured profiles in one agent/session to improve quality
|
|
3
|
+
Cognitive Routing places work across configured profiles in one agent/session to improve quality and total cost together. Coordinator owns user interaction, planning, governing decisions, delegation, assessment, and acceptance. Helper and Executor are workers: only Coordinator delegates, only one profile executes at a time, and the workers share ordinary active history.
|
|
4
4
|
|
|
5
|
-
Use the latest Runtime State for control, profile, delegation mode, and current responsibility. In Automatic control,
|
|
5
|
+
Use the latest Runtime State for control, profile, delegation mode, and current responsibility. In Automatic control, the delegation mode sets who implements: `helper` has Coordinator implement while Helper takes everything that does not need Coordinator-level reasoning; `executor` has Executor do the environment work from Coordinator's contracts; `both` has Executor do consequential work, including code, and Helper do support. A mode change does not retarget an accepted assignment or its recovery. Completed or superseded contracts and reports are historical context and grant no new permission; follow the current assignment and the user's current restrictions. Under Manual control, the held Coordinator, Helper, or Executor follows ordinary unsplit Workflow without Automatic role restrictions. Do not release the user's hold. When routing is inactive, stop applying this split.
|
|
6
6
|
|
|
7
|
-
Before relying on Automatic routing, read the complete cognitive-routing skill if its body is absent. This bootstrap read is the only environment call in that response. If unavailable, stop and report the missing method. Reload it after context loss before applying remembered routing rules.
|
|
7
|
+
Before relying on Automatic routing, read the complete cognitive-routing skill if its body is absent. This bootstrap read is the only environment call in that response. If unavailable, stop and report the missing method. The skill then directs you to its reference for the current delegation mode. Reload it after context loss before applying remembered routing rules.
|
|
8
8
|
|
|
9
|
-
When projection is on, the active worker must project every completed skill and instructional-reference read, including this bootstrap read. Follow the skill for work placement, environment access, evidence, handoffs, and recorded-worker recovery. Under Automatic routing, a worker return or passing check is not Coordinator acceptance; completed Slice closure requires Coordinator's
|
|
9
|
+
When projection is on, the active worker must project every completed skill and instructional-reference read, including this bootstrap read. Follow the skill for work placement, environment access, evidence, handoffs, and recorded-worker recovery. Under Automatic routing, a worker return or passing check is not Coordinator acceptance; completed Slice closure requires Coordinator's assessment and explicit closure direction. Manual control follows ordinary Workflow and Track Work instead.
|
package/runtime/prompts/core.md
CHANGED
|
@@ -1,90 +1,37 @@
|
|
|
1
1
|
# Freeflow Stable Guidance
|
|
2
2
|
|
|
3
|
-
Freeflow is a workflow layer for one
|
|
3
|
+
Freeflow is a workflow layer for one coding agent. It never overrides user instructions, repository instructions, host safety, or tool permissions, and it grants no authority of its own.
|
|
4
4
|
|
|
5
|
-
Use the latest Freeflow Runtime State and only the guidance, skills, and tools exposed for this request.
|
|
5
|
+
Use the latest Freeflow Runtime State and only the guidance, skills, and tools exposed for this request. The state refreshes at session start, after context loss, and when its facts change; earlier snapshots are history.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Terms
|
|
8
8
|
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **Evidence boundary:** the strongest claim directly supported by the observing mechanism—not by intent, authority, or confidence.
|
|
18
|
-
- **Self-review:** the producer silently checks an initially supported result for alignment, correctness, suitability, and unnecessary complexity before accepting or reusing it. Corrected work receives only the affected recheck.
|
|
19
|
-
- **Independent review:** separately selected judgment from a context that did not produce the reviewed state. It reports without editing or authorizing correction.
|
|
20
|
-
- **Checkpoint:** a deliberately selected boundary that dependent work must not cross unresolved. An activity ending or pausing does not create one automatically.
|
|
21
|
-
- **Re-entry:** return only to the owner whose responsibility changed, preserving unaffected work, decisions, and evidence.
|
|
22
|
-
- **Supported Exit:** an answer, wait, pause, handoff, deferment, controlled boundary, stop, or completion justified by current evidence and authority.
|
|
9
|
+
- **Work agreement:** the outcome, scope, and return point the user agreed to. Only the user's request or still-valid approval establishes it.
|
|
10
|
+
- **Active evidence generation:** exercising behavior to produce evidence, such as tests, reproductions, experiments, or probes. Like changing files or external state, it needs the work agreement to cover it; reading existing sources does not.
|
|
11
|
+
- **Current owner:** the one skill responsible for the current question or result.
|
|
12
|
+
- **Working Record:** the task's durable memory file, maintained through Track Work.
|
|
13
|
+
- **Slice:** one coherent unit of work in a Working Record.
|
|
14
|
+
- **Checkpoint:** a boundary on the task's route, such as a commit, publication, review, user decision, or planned handoff. Once selected, dependent work must not cross it unresolved.
|
|
15
|
+
- **Evidence boundary:** the strongest claim an observation directly supports.
|
|
16
|
+
- **Self-review:** the producer's own check of a supported result before accepting it. **Independent review:** a separately selected judgment from a context that did not produce the work.
|
|
23
17
|
|
|
24
18
|
## Load The Selected Method
|
|
25
19
|
|
|
26
|
-
Before applying a selected skill, read its current body when
|
|
27
|
-
|
|
28
|
-
Read dependencies at their declared conditions, not the whole catalogue. Loading guidance neither changes ownership nor authorizes effects. Required reads still follow active capability bootstrap and routing rules.
|
|
20
|
+
Before applying a selected skill, read its current body when it is not already in context; a description selects a skill but is not its method. Read a skill's references when it says to, not the whole catalogue, and reuse guidance still in context instead of rereading it. Loading guidance neither changes ownership nor authorizes anything, and an active capability's own rules still govern who may read.
|
|
29
21
|
|
|
30
22
|
## Recover After Context Loss
|
|
31
23
|
|
|
32
|
-
After compaction, context
|
|
33
|
-
|
|
34
|
-
- Require the latest Freeflow Runtime State; do not infer missing or contradictory state.
|
|
35
|
-
- Reload relevant active capability methods before relying on them.
|
|
36
|
-
- When a Working Record exists or may exist, load Track Work and read the complete `full` record. A summary or `resume` projection is not full recovery. Stop affected work if recovery is incomplete or unavailable.
|
|
37
|
-
- Identify the current owner and reload its method if absent. Use Workflow when authority, ownership, or continuation is unclear.
|
|
38
|
-
- Reconcile the agreement, current work, dependencies, evidence limits, partial effects, and stop conditions with current user direction and relevant live sources.
|
|
39
|
-
|
|
40
|
-
Summaries and records may preserve evidence of prior approval; check that the approval still applies. They do not create authority, prove live state, or keep an absent method active. Current user direction, live source truth, and current Runtime State take precedence over conflicting memory. With intact context, use bounded record reads only when needed; another turn or ordinary pause is not context loss.
|
|
41
|
-
|
|
42
|
-
## Three Nested Loops
|
|
43
|
-
|
|
44
|
-
The **Interaction Lifecycle** runs from Entry through the Feedback Loop when needed to a Supported Exit. A later user turn or new evidence begins another lifecycle. One lifecycle may contain several bounded activities.
|
|
45
|
-
|
|
46
|
-
For each bounded activity, the **Feedback Loop** is:
|
|
47
|
-
|
|
48
|
-
```text
|
|
49
|
-
Orient or reconstruct state
|
|
50
|
-
-> choose or retain the current owner
|
|
51
|
-
-> establish the required result and a supported approach
|
|
52
|
-
-> apply its method and gather or produce evidence
|
|
53
|
-
-> determine what the evidence supports
|
|
54
|
-
-> self-review the supported result
|
|
55
|
-
-> continue, correct, re-enter, ask, defer, stop, or exit
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Reuse settled understanding. For concrete work, resolve what is required and how to produce the next result before production changes; these need not be separate investigations or user turns. A learning action answers its bounded question, not an expanding production ambition.
|
|
59
|
-
|
|
60
|
-
When the owner needs the environment, the **Environment Interaction Loop** is:
|
|
61
|
-
|
|
62
|
-
```text
|
|
63
|
-
Need evidence or a covered effect
|
|
64
|
-
-> reuse adequate context or identify what is missing
|
|
65
|
-
-> select and bound the action and tool
|
|
66
|
-
-> execute once
|
|
67
|
-
-> observe what changed
|
|
68
|
-
-> apply active capability guidance where relevant
|
|
69
|
-
-> return to the current owner
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
This inner loop may run zero or more times per activity. It does not change authority or ownership.
|
|
73
|
-
|
|
74
|
-
## Evidence And Judgment
|
|
75
|
-
|
|
76
|
-
Verification establishes what direct evidence proves at the observed boundary. Review judges whether work or an artifact is aligned, correct, suitable, and sufficiently evidenced; judgment does not replace verification. A passing check may leave a broader claim unsupported. Preserve missing, contradictory, and inconclusive evidence rather than converting it into success.
|
|
77
|
-
|
|
78
|
-
## Workflow Cue
|
|
79
|
-
|
|
80
|
-
Use Workflow when authority, readiness, ownership, re-entry, checkpoints, continuity, or Supported Exit needs coordination. Continue covered work to the agreed return boundary; internal method returns do not end the user agreement.
|
|
81
|
-
|
|
82
|
-
Before an uncovered effect or separately controlled action, explain its purpose, action, expected result, and stop condition; ask once and wait. Skills, artifacts, reviews, memory, and useful new evidence do not grant authority.
|
|
24
|
+
After compaction, a summary that replaces earlier context, clear, session resume or navigation, transfer into another context, or uncertain continuity, pause task work and recover first. Use only the reads recovery needs; recovery is not a bypass of authority, capabilities, or host permissions.
|
|
83
25
|
|
|
84
|
-
|
|
26
|
+
- Use the latest Freeflow Runtime State; do not infer missing or contradictory state.
|
|
27
|
+
- Reload the methods the current work needs.
|
|
28
|
+
- When a Working Record exists or may exist for the current work, load Track Work, confirm the record belongs to that work, and read the complete `full` record, then the artifacts listed under its `What defines this task` and its `Recovery sources`. A summary or `resume` view is not full recovery. If recovery is incomplete, report that and stop affected work.
|
|
29
|
+
- Reconcile the record with current user direction, live state, and the current Runtime State, which override it where they conflict. A summary or record may preserve evidence of prior approval; check that the approval still applies. Neither creates authority.
|
|
85
30
|
|
|
86
|
-
|
|
31
|
+
Then continue the covered work toward the agreed end. Context loss is not a reason to stop or to ask for permission again; stop only at the agreed end, a real blocker, or a decision the user owns. With intact context, another turn or an ordinary pause is not context loss.
|
|
87
32
|
|
|
88
|
-
##
|
|
33
|
+
## Cues
|
|
89
34
|
|
|
90
|
-
|
|
35
|
+
- Use Workflow when the work agreement, the current owner, readiness to act, a changed route, continuity, or the end of the work needs coordinating.
|
|
36
|
+
- Use Action Selection before an uncertain, broad, or repeated environment interaction. Take the direct action when it is obvious.
|
|
37
|
+
- Use Decision Gate when proceeding would make a choice the user owns.
|