@hassangameryt/freeflow 0.6.0 → 0.7.1

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.
Files changed (136) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +3 -2
  3. package/.codex-plugin/plugin.json +5 -6
  4. package/.cursor-plugin/plugin.json +25 -0
  5. package/README.md +66 -56
  6. package/capabilities/cognitive-routing/SKILL.md +288 -130
  7. package/com.github.copilot/hooks/hooks.json +11 -0
  8. package/command-surface.json +0 -24
  9. package/gemini-extension.json +5 -0
  10. package/hooks/adapters/claude-session-start.mjs +16 -0
  11. package/hooks/adapters/codex-session-start.mjs +16 -0
  12. package/hooks/adapters/copilot-session-start.mjs +13 -0
  13. package/hooks/adapters/cursor-session-start.mjs +13 -0
  14. package/hooks/adapters/gemini-session-start.mjs +15 -0
  15. package/hooks/claude/hooks.json +18 -0
  16. package/hooks/codex/hooks.json +18 -0
  17. package/hooks/cursor/hooks.json +9 -0
  18. package/hooks/freeflow-runtime-context.mjs +3 -650
  19. package/hooks/hooks.json +4 -30
  20. package/hooks/shared/runtime-context.mjs +210 -0
  21. package/hooks/tests/check-host-adapters.test.mjs +82 -0
  22. package/hooks/tests/check-runtime-context-hook.sh +140 -320
  23. package/package.json +20 -4
  24. package/pi-extension/dist/cognitive-routing/commands.js +3 -3
  25. package/pi-extension/dist/cognitive-routing/config.js +16 -20
  26. package/pi-extension/dist/cognitive-routing/controller.js +478 -48
  27. package/pi-extension/dist/cognitive-routing/history.js +12 -0
  28. package/pi-extension/dist/cognitive-routing/host.js +74 -12
  29. package/pi-extension/dist/cognitive-routing/pi-routing-session.js +414 -0
  30. package/pi-extension/dist/cognitive-routing/pi-session-control.js +320 -0
  31. package/pi-extension/dist/cognitive-routing/tool.js +1 -0
  32. package/pi-extension/dist/cognitive-routing/types.js +1 -1
  33. package/pi-extension/dist/index.js +236 -219
  34. package/pi-extension/dist/runtime/runtime-context.js +126 -286
  35. package/pi-extension/dist/settings/settings-ui.js +52 -336
  36. package/pi-extension/src/cognitive-routing/commands.ts +15 -5
  37. package/pi-extension/src/cognitive-routing/config.ts +16 -20
  38. package/pi-extension/src/cognitive-routing/controller.ts +561 -51
  39. package/pi-extension/src/cognitive-routing/history.ts +12 -0
  40. package/pi-extension/src/cognitive-routing/host.ts +110 -16
  41. package/pi-extension/src/cognitive-routing/pi-routing-session.ts +538 -0
  42. package/pi-extension/src/cognitive-routing/pi-session-control.ts +444 -0
  43. package/pi-extension/src/cognitive-routing/tool.ts +1 -0
  44. package/pi-extension/src/cognitive-routing/types.ts +1 -1
  45. package/pi-extension/src/index.ts +249 -239
  46. package/pi-extension/src/runtime/runtime-context.ts +129 -305
  47. package/pi-extension/src/settings/settings-ui.ts +53 -371
  48. package/pi-extension/tests/cognitive-routing/commands.test.js +1 -1
  49. package/pi-extension/tests/cognitive-routing/controller.test.js +69 -40
  50. package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +128 -57
  51. package/pi-extension/tests/cognitive-routing/pi-normal-lifecycle.test.js +434 -0
  52. package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +174 -0
  53. package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +91 -0
  54. package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +47 -4
  55. package/pi-extension/tests/integration/mode-free-core.test.js +218 -0
  56. package/pi-extension/tests/integration/pi-extension.test.js +430 -2478
  57. package/pi-extension/tests/integration/prompt-architecture.test.js +124 -79
  58. package/pi-extension/tests/integration/unaffected-regressions.test.js +411 -0
  59. package/plugin.json +33 -0
  60. package/runtime/prompts/cognitive-routing.md +5 -4
  61. package/runtime/prompts/conversation-history.md +1 -1
  62. package/runtime/prompts/core.md +77 -6
  63. package/runtime/prompts/interaction-contract.md +6 -2
  64. package/skills/action-selection/SKILL.md +93 -44
  65. package/skills/action-selection/references/trajectory-stalls.md +71 -0
  66. package/skills/bypass/SKILL.md +109 -39
  67. package/skills/commit-work/SKILL.md +94 -55
  68. package/skills/commit-work/references/git-state-edges.md +94 -0
  69. package/skills/commit-work/references/simple-push.md +92 -0
  70. package/skills/commit-work/references/staging-decisions.md +79 -46
  71. package/skills/decision-gate/SKILL.md +80 -22
  72. package/skills/design-for-depth/SKILL.md +208 -43
  73. package/skills/design-for-depth/references/design-pressure-signals.md +108 -240
  74. package/skills/design-for-depth/references/interface-design-loop.md +77 -97
  75. package/skills/design-for-depth/references/module-and-dependency-design.md +147 -0
  76. package/skills/design-for-depth/references/software-design-philosophy.md +127 -173
  77. package/skills/design-for-depth/references/state-and-failure-boundaries.md +136 -0
  78. package/skills/diagnose-failure/SKILL.md +198 -52
  79. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +19 -5
  80. package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +13 -5
  81. package/skills/discuss/SKILL.md +118 -76
  82. package/skills/discuss/references/discussion-continuity.md +84 -32
  83. package/skills/evaluate-skill/SKILL.md +166 -52
  84. package/skills/evaluate-skill/references/evaluation-definition-schema.md +2 -2
  85. package/skills/evaluate-skill/references/evaluation-design.md +56 -30
  86. package/skills/evaluate-skill/references/execution-and-evidence.md +1 -1
  87. package/skills/evaluate-skill/references/review-and-revision.md +62 -21
  88. package/skills/execute-work/SKILL.md +98 -59
  89. package/skills/execute-work/references/code-practices.md +4 -4
  90. package/skills/execute-work/references/execute-work-edges.md +42 -22
  91. package/skills/review-artifact/SKILL.md +132 -71
  92. package/skills/review-artifact/references/adjudicate-artifact-review.md +101 -0
  93. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +52 -41
  94. package/skills/review-work/SKILL.md +115 -72
  95. package/skills/review-work/references/adjudicate-work-review.md +97 -0
  96. package/skills/review-work/references/independent-work-reviewer-contract.md +40 -35
  97. package/skills/review-work/references/security-risk-lens.md +17 -15
  98. package/skills/setup-freeflow/SKILL.md +14 -17
  99. package/skills/setup-freeflow/references/activation-contract.md +24 -30
  100. package/skills/setup-freeflow/references/host-setup.md +6 -7
  101. package/skills/simplify-code/SKILL.md +97 -39
  102. package/skills/simplify-code/references/simplification-patterns.md +1 -1
  103. package/skills/tdd/SKILL.md +108 -41
  104. package/skills/tdd/references/test-design.md +1 -1
  105. package/skills/track-work/SKILL.md +133 -300
  106. package/skills/track-work/references/working-record-format.md +515 -0
  107. package/skills/track-work/scripts/lib/document.mjs +713 -0
  108. package/skills/track-work/scripts/lib/format.mjs +166 -0
  109. package/skills/track-work/scripts/lib/store.mjs +233 -0
  110. package/skills/track-work/scripts/lib/transitions.mjs +790 -0
  111. package/skills/track-work/scripts/lib/views.mjs +27 -430
  112. package/skills/track-work/scripts/working-record.mjs +292 -98
  113. package/skills/verify-work/SKILL.md +46 -20
  114. package/skills/verify-work/references/browser-runtime-evidence.md +1 -1
  115. package/skills/verify-work/references/integration-evidence.md +1 -7
  116. package/skills/verify-work/references/performance-evidence.md +1 -1
  117. package/skills/workflow/SKILL.md +110 -80
  118. package/skills/workflow/references/domain-skill-composition.md +24 -18
  119. package/skills/write-skill/SKILL.md +218 -62
  120. package/skills/write-skill/references/activation-boundaries.md +63 -21
  121. package/skills/write-skill/references/agent-first-instructions.md +61 -27
  122. package/skills/write-skill/references/progressive-disclosure.md +91 -41
  123. package/skills/write-skill/references/skill-dependency-graphs.md +190 -0
  124. package/runtime/prompts/skills.md +0 -65
  125. package/skills/mode-contract/SKILL.md +0 -104
  126. package/skills/track-work/scripts/lib/codec.mjs +0 -570
  127. package/skills/track-work/scripts/lib/commands.mjs +0 -568
  128. package/skills/track-work/scripts/lib/contract.mjs +0 -303
  129. package/skills/track-work/scripts/lib/model.mjs +0 -506
  130. package/skills/track-work/scripts/lib/operations.mjs +0 -965
  131. package/skills/track-work/scripts/lib/record-store.mjs +0 -264
  132. package/skills/track-work/scripts/lib/result.mjs +0 -37
  133. package/skills/track-work/scripts/lib/rewrites.mjs +0 -740
  134. package/skills/track-work/scripts/lib/schema.mjs +0 -277
  135. package/skills/track-work/scripts/lib/source-inventory.mjs +0 -162
  136. package/skills/track-work/scripts/lib/workspace.mjs +0 -134
@@ -0,0 +1,210 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import process from "node:process";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
+
6
+ const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
7
+ export const PLUGIN_ROOT = path.resolve(SCRIPT_DIR, "../..");
8
+
9
+ export function readHookInput() {
10
+ const raw = fs.readFileSync(0, "utf8").trim();
11
+ if (!raw) return { input: {}, raw: "" };
12
+
13
+ try {
14
+ return { input: JSON.parse(raw), raw };
15
+ } catch {
16
+ return { input: {}, raw };
17
+ }
18
+ }
19
+
20
+ function readText(filePath) {
21
+ try {
22
+ const text = fs.readFileSync(filePath, "utf8").trim();
23
+ return text || null;
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+
29
+ export function findWorkspaceRoot(cwd) {
30
+ let current = path.resolve(cwd || process.cwd());
31
+
32
+ while (true) {
33
+ if (fs.existsSync(path.join(current, ".git"))) return current;
34
+
35
+ const parent = path.dirname(current);
36
+ if (parent === current) return path.resolve(cwd || process.cwd());
37
+ current = parent;
38
+ }
39
+ }
40
+
41
+ function readConfigLayer(filePath, validate) {
42
+ if (!fs.existsSync(filePath)) {
43
+ return { exists: false, valid: false, parsed: {}, error: null };
44
+ }
45
+
46
+ try {
47
+ const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
48
+ const error = validate(parsed);
49
+ return {
50
+ exists: true,
51
+ valid: error === null,
52
+ parsed: error === null ? parsed : {},
53
+ error,
54
+ };
55
+ } catch (error) {
56
+ return {
57
+ exists: true,
58
+ valid: false,
59
+ parsed: {},
60
+ error: error instanceof Error ? error.message : String(error),
61
+ };
62
+ }
63
+ }
64
+
65
+ function isRecord(value) {
66
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
67
+ }
68
+
69
+ function validateCoreConfigFields(value) {
70
+ if (Object.hasOwn(value, "enabled") && typeof value.enabled !== "boolean") {
71
+ return "enabled must be a boolean";
72
+ }
73
+ if (Object.hasOwn(value, "contextVirtualization") && typeof value.contextVirtualization !== "boolean") {
74
+ return "contextVirtualization must be a boolean";
75
+ }
76
+ if (Object.hasOwn(value, "conversationHistory") && typeof value.conversationHistory !== "boolean") {
77
+ return "conversationHistory must be a boolean";
78
+ }
79
+ return null;
80
+ }
81
+
82
+ function isValidSetupConfig(value) {
83
+ if (!isRecord(value)) return "repository config must be a JSON object";
84
+
85
+ const allowedKeys = new Set([
86
+ "enabled",
87
+ "contextVirtualization",
88
+ "conversationHistory",
89
+ "outputRouter",
90
+ "observedRouting",
91
+ "scriptTransform",
92
+ "cognitiveRouting",
93
+ ]);
94
+ if (!Object.keys(value).every((key) => allowedKeys.has(key))) {
95
+ return "repository config contains unsupported top-level keys";
96
+ }
97
+
98
+ const coreError = validateCoreConfigFields(value);
99
+ if (coreError) return coreError;
100
+
101
+ for (const key of ["outputRouter", "observedRouting", "scriptTransform"]) {
102
+ if (Object.hasOwn(value, key) && !isRecord(value[key])) return `${key} must be an object`;
103
+ }
104
+ return null;
105
+ }
106
+
107
+ function isValidLocalConfig(value) {
108
+ if (!isRecord(value)) return "local config must be a JSON object";
109
+
110
+ const allowedKeys = new Set([
111
+ "enabled",
112
+ "processing",
113
+ "cognitiveRouting",
114
+ "contextVirtualization",
115
+ "conversationHistory",
116
+ ]);
117
+ if (!Object.keys(value).every((key) => allowedKeys.has(key))) {
118
+ return "local config contains unsupported top-level keys";
119
+ }
120
+ return validateCoreConfigFields(value);
121
+ }
122
+
123
+ function resolveLayeredValue(repository, local, key, fallback) {
124
+ if (Object.hasOwn(local, key)) return local[key];
125
+ if (Object.hasOwn(repository, key)) return repository[key];
126
+ return fallback;
127
+ }
128
+
129
+ function resolveCoreConfig(repository, local) {
130
+ return {
131
+ enabled: resolveLayeredValue(repository, local, "enabled", true),
132
+ contextVirtualization: resolveLayeredValue(repository, local, "contextVirtualization", false),
133
+ conversationHistory: resolveLayeredValue(repository, local, "conversationHistory", false),
134
+ };
135
+ }
136
+
137
+ export function readConfig(root) {
138
+ const repository = readConfigLayer(path.join(root, ".freeflow", "config.json"), isValidSetupConfig);
139
+ const local = readConfigLayer(path.join(root, ".freeflow", "local.json"), isValidLocalConfig);
140
+ const localValid = !local.exists || local.valid;
141
+ const configured = repository.valid && localValid;
142
+ const core = resolveCoreConfig(repository.valid ? repository.parsed : {}, local.valid ? local.parsed : {});
143
+ const enabled = configured && core.enabled;
144
+
145
+ return {
146
+ exists: repository.exists,
147
+ valid: configured,
148
+ configured,
149
+ repositoryValid: repository.valid,
150
+ localExists: local.exists,
151
+ localValid,
152
+ error: repository.valid ? local.error : repository.error,
153
+ enabled,
154
+ contextVirtualizationEnabled: enabled && core.contextVirtualization,
155
+ conversationHistoryEnabled: enabled && core.conversationHistory,
156
+ };
157
+ }
158
+
159
+ function loadRuntimeContext() {
160
+ const corePrompt = readText(path.join(PLUGIN_ROOT, "runtime", "prompts", "core.md"));
161
+ const interactionContract = readText(path.join(PLUGIN_ROOT, "runtime", "prompts", "interaction-contract.md"));
162
+
163
+ if (!corePrompt || !interactionContract) {
164
+ throw new Error("Freeflow core runtime prompt files are missing.");
165
+ }
166
+
167
+ return { corePrompt, interactionContract };
168
+ }
169
+
170
+ function runtimeStateContext() {
171
+ return ["# Freeflow Runtime State", "", "Freeflow: active"].join("\n");
172
+ }
173
+
174
+ export function renderRuntimeContext(root) {
175
+ const config = readConfig(root);
176
+ if (!config.valid || !config.enabled) return "";
177
+
178
+ const { corePrompt, interactionContract } = loadRuntimeContext();
179
+ return [corePrompt, interactionContract, runtimeStateContext()].filter(Boolean).join("\n\n");
180
+ }
181
+
182
+ export function emitHookOutput(output) {
183
+ process.stdout.write(`${JSON.stringify(output)}\n`);
184
+ }
185
+
186
+ export function runSessionStartAdapter({ eventNames, getWorkspaceCwd, formatOutput }) {
187
+ const { input, raw } = readHookInput();
188
+ const eventName = process.argv[2] || input.hook_event_name || "";
189
+ if ((!raw && !eventName) || process.env.FREEFLOW_DISABLE_RUNTIME_CONTEXT === "1") return;
190
+ if (!eventNames.includes(eventName)) return;
191
+
192
+ const root = findWorkspaceRoot(getWorkspaceCwd(input));
193
+ const additionalContext = renderRuntimeContext(root);
194
+ if (additionalContext.trim()) {
195
+ emitHookOutput(formatOutput({ eventName, input, additionalContext }));
196
+ }
197
+ }
198
+
199
+ export function runSafely(run) {
200
+ try {
201
+ run();
202
+ } catch (error) {
203
+ const message = error instanceof Error ? error.message : String(error);
204
+ process.stderr.write(`Freeflow runtime context hook skipped: ${message}\n`);
205
+ }
206
+ }
207
+
208
+ export function isMainModule(metaUrl) {
209
+ return process.argv[1] && pathToFileURL(path.resolve(process.argv[1])).href === metaUrl;
210
+ }
@@ -0,0 +1,82 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join, resolve } from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+ import test from "node:test";
7
+
8
+ const repoRoot = resolve(import.meta.dirname, "../..");
9
+ const adapters = {
10
+ legacy: ["hooks/freeflow-runtime-context.mjs", "hookSpecificOutput.additionalContext"],
11
+ claude: ["hooks/adapters/claude-session-start.mjs", "hookSpecificOutput.additionalContext"],
12
+ codex: ["hooks/adapters/codex-session-start.mjs", "hookSpecificOutput.additionalContext"],
13
+ gemini: ["hooks/adapters/gemini-session-start.mjs", "hookSpecificOutput.additionalContext"],
14
+ copilot: ["hooks/adapters/copilot-session-start.mjs", "additionalContext"],
15
+ cursor: ["hooks/adapters/cursor-session-start.mjs", "additional_context"],
16
+ };
17
+
18
+ function createWorkspace(config = "{}\n") {
19
+ const root = mkdtempSync(join(tmpdir(), "freeflow-host-adapter-"));
20
+ mkdirSync(join(root, ".freeflow"), { recursive: true });
21
+ writeFileSync(join(root, ".freeflow", "config.json"), config, "utf8");
22
+ return root;
23
+ }
24
+
25
+ function runAdapter(relativePath, workspace, input, event = "SessionStart") {
26
+ const result = spawnSync(process.execPath, [join(repoRoot, relativePath), event], {
27
+ cwd: repoRoot,
28
+ input: `${JSON.stringify(input)}\n`,
29
+ encoding: "utf8",
30
+ });
31
+ assert.equal(result.status, 0, `${relativePath} stderr: ${result.stderr}`);
32
+ assert.equal(result.stderr, "", `${relativePath} should not log on the happy path`);
33
+ return result.stdout.trim() ? JSON.parse(result.stdout) : undefined;
34
+ }
35
+
36
+ test("host adapters share rendered mandatory context but emit host-native envelopes", () => {
37
+ const workspace = createWorkspace();
38
+ const outputs = {};
39
+ for (const [host, [relativePath]] of Object.entries(adapters)) {
40
+ const input =
41
+ host === "cursor"
42
+ ? { hook_event_name: "sessionStart", workspace_roots: [workspace] }
43
+ : { hook_event_name: "SessionStart", cwd: workspace };
44
+ outputs[host] = runAdapter(relativePath, workspace, input, host === "cursor" ? "sessionStart" : "SessionStart");
45
+ assert.ok(outputs[host], `${host} should emit context`);
46
+ }
47
+
48
+ const legacyContext = outputs.legacy.hookSpecificOutput.additionalContext;
49
+ for (const host of ["claude", "codex", "gemini"]) {
50
+ assert.equal(outputs[host].hookSpecificOutput.additionalContext, legacyContext);
51
+ }
52
+ assert.deepEqual(Object.keys(outputs.copilot).sort(), ["additionalContext"]);
53
+ assert.equal(outputs.copilot.additionalContext, legacyContext);
54
+ assert.equal(outputs.copilot.hookSpecificOutput, undefined);
55
+ assert.equal(outputs.cursor.additional_context, legacyContext);
56
+ assert.equal(outputs.cursor.hookSpecificOutput, undefined);
57
+ assert.match(legacyContext, /# Freeflow Stable Guidance/);
58
+ assert.match(legacyContext, /# Freeflow Interaction Contract/);
59
+ assert.match(legacyContext, /Freeflow: active/);
60
+ assert.doesNotMatch(legacyContext, /Cognitive Routing/);
61
+ });
62
+
63
+ test("host adapters stay inert for disabled, invalid, and unsupported lifecycle inputs", () => {
64
+ const disabledWorkspace = createWorkspace('{"enabled":false}\n');
65
+ const invalidWorkspace = createWorkspace('{"enabled":"no"}\n');
66
+ for (const [host, [relativePath]] of Object.entries(adapters)) {
67
+ const input =
68
+ host === "cursor"
69
+ ? { hook_event_name: "sessionStart", workspace_roots: [disabledWorkspace] }
70
+ : { hook_event_name: "SessionStart", cwd: disabledWorkspace };
71
+ assert.equal(
72
+ runAdapter(relativePath, disabledWorkspace, input, host === "cursor" ? "sessionStart" : "SessionStart"),
73
+ undefined,
74
+ );
75
+ const invalidInput = { ...input, cwd: invalidWorkspace, workspace_roots: [invalidWorkspace] };
76
+ assert.equal(
77
+ runAdapter(relativePath, invalidWorkspace, invalidInput, host === "cursor" ? "sessionStart" : "SessionStart"),
78
+ undefined,
79
+ );
80
+ assert.equal(runAdapter(relativePath, disabledWorkspace, input, "UserPromptSubmit"), undefined);
81
+ }
82
+ });