@kontourai/flow-agents 0.1.2 → 0.3.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.
Files changed (117) hide show
  1. package/.github/dependabot.yml +23 -0
  2. package/.github/workflows/release-please.yml +31 -0
  3. package/.github/workflows/runtime-compat.yml +118 -0
  4. package/CHANGELOG.md +46 -0
  5. package/CONTRIBUTING.md +4 -0
  6. package/README.md +80 -18
  7. package/build/src/cli/flow-kit.js +9 -4
  8. package/build/src/cli/init.js +215 -5
  9. package/build/src/cli/runtime-adapter.js +9 -5
  10. package/build/src/cli/telemetry-doctor.js +4 -1
  11. package/build/src/cli/utterance-check.js +65 -1
  12. package/build/src/runtime-adapters.js +34 -0
  13. package/build/src/tools/build-universal-bundles.js +285 -0
  14. package/build/src/tools/filter-installed-packs.js +3 -0
  15. package/build/src/tools/validate-source-tree.js +5 -1
  16. package/console.telemetry.json +115 -20
  17. package/context/scripts/telemetry/lib/config.sh +5 -1
  18. package/context/settings/flow-agents-settings.json +7 -0
  19. package/docs/_layouts/default.html +2 -0
  20. package/docs/context-map.md +1 -0
  21. package/docs/index.md +53 -4
  22. package/docs/integrations/conformance.md +246 -0
  23. package/docs/integrations/framework-adapter.md +275 -0
  24. package/docs/integrations/harness-install.md +213 -0
  25. package/docs/integrations/index.md +58 -0
  26. package/docs/integrations/knowledge-kit-live.md +211 -0
  27. package/docs/kit-authoring-guide.md +169 -0
  28. package/docs/north-star.md +2 -2
  29. package/docs/spec/runtime-hook-surface.md +525 -0
  30. package/docs/survey-utterance-check.md +211 -94
  31. package/docs/vision.md +45 -0
  32. package/evals/acceptance/run.sh +13 -2
  33. package/evals/acceptance/test_knowledge_kit_live.sh +221 -0
  34. package/evals/acceptance/test_opencode_harness.sh +121 -0
  35. package/evals/acceptance/test_pi_harness.sh +113 -0
  36. package/evals/integration/test_bundle_install.sh +226 -1
  37. package/evals/integration/test_bundle_lifecycle.sh +641 -0
  38. package/evals/integration/test_runtime_adapter_activation.sh +113 -1
  39. package/evals/integration/test_utterance_check.sh +291 -44
  40. package/evals/run.sh +2 -0
  41. package/evals/static/test_universal_bundles.sh +137 -2
  42. package/integrations/strands/README.md +256 -0
  43. package/integrations/strands/example.py +74 -0
  44. package/integrations/strands/examples/knowledge_kit_live.py +461 -0
  45. package/integrations/strands/flow_agents_strands/__init__.py +27 -0
  46. package/integrations/strands/flow_agents_strands/hooks.py +194 -0
  47. package/integrations/strands/flow_agents_strands/policy.py +348 -0
  48. package/integrations/strands/flow_agents_strands/steering.py +225 -0
  49. package/integrations/strands/flow_agents_strands/telemetry.py +238 -0
  50. package/integrations/strands/pyproject.toml +38 -0
  51. package/integrations/strands/tests/__init__.py +0 -0
  52. package/integrations/strands/tests/test_hooks.py +392 -0
  53. package/integrations/strands/tests/test_policy.py +315 -0
  54. package/integrations/strands/tests/test_telemetry.py +184 -0
  55. package/integrations/strands-ts/README.md +224 -0
  56. package/integrations/strands-ts/bin/conformance-shim.mjs +257 -0
  57. package/integrations/strands-ts/package.json +53 -0
  58. package/integrations/strands-ts/src/hooks.ts +312 -0
  59. package/integrations/strands-ts/src/index.ts +22 -0
  60. package/integrations/strands-ts/src/policy.ts +345 -0
  61. package/integrations/strands-ts/src/telemetry.ts +251 -0
  62. package/integrations/strands-ts/test/test-policy.ts +322 -0
  63. package/integrations/strands-ts/test/test-steering.ts +159 -0
  64. package/integrations/strands-ts/test/test-telemetry.ts +226 -0
  65. package/integrations/strands-ts/tsconfig.json +20 -0
  66. package/kits/catalog.json +6 -0
  67. package/kits/knowledge/adapters/default-store/index.js +821 -0
  68. package/kits/knowledge/adapters/flow-runner/index.js +1179 -0
  69. package/kits/knowledge/adapters/flow-runner/telemetry.js +174 -0
  70. package/kits/knowledge/docs/README.md +135 -0
  71. package/kits/knowledge/docs/store-contract.md +526 -0
  72. package/kits/knowledge/evals/consolidation/suite.test.js +1234 -0
  73. package/kits/knowledge/evals/contract-suite/suite.test.js +670 -0
  74. package/kits/knowledge/evals/ingest-compile/suite.test.js +574 -0
  75. package/kits/knowledge/evals/synthesis/suite.test.js +909 -0
  76. package/kits/knowledge/flows/compile.flow.json +60 -0
  77. package/kits/knowledge/flows/consolidate.flow.json +77 -0
  78. package/kits/knowledge/flows/ingest.flow.json +60 -0
  79. package/kits/knowledge/flows/store-contract.flow.json +48 -0
  80. package/kits/knowledge/flows/synthesize.flow.json +77 -0
  81. package/kits/knowledge/kit.json +78 -0
  82. package/package.json +7 -2
  83. package/packaging/conformance/README.md +142 -0
  84. package/packaging/conformance/fixtures/config-protection--allow-no-path.json +18 -0
  85. package/packaging/conformance/fixtures/config-protection--allow-safe-file.json +20 -0
  86. package/packaging/conformance/fixtures/config-protection--block-biome.json +20 -0
  87. package/packaging/conformance/fixtures/config-protection--block-eslintrc.json +20 -0
  88. package/packaging/conformance/fixtures/quality-gate--allow-no-path.json +17 -0
  89. package/packaging/conformance/fixtures/quality-gate--allow-nonexistent-file.json +19 -0
  90. package/packaging/conformance/fixtures/stop-goal-fit--allow-clean-cwd.json +17 -0
  91. package/packaging/conformance/fixtures/stop-goal-fit--block-strict-mode.json +23 -0
  92. package/packaging/conformance/fixtures/stop-goal-fit--warn-active-delivery.json +21 -0
  93. package/packaging/conformance/fixtures/workflow-steering--allow-no-state.json +16 -0
  94. package/packaging/conformance/fixtures/workflow-steering--inject-active-state.json +29 -0
  95. package/packaging/conformance/fixtures/workflow-steering--inject-subagent-steering.json +25 -0
  96. package/packaging/conformance/package.json +4 -0
  97. package/packaging/conformance/run-conformance.js +322 -0
  98. package/packaging/manifest.json +59 -0
  99. package/schemas/flow-agents-settings.schema.json +48 -0
  100. package/scripts/README.md +4 -0
  101. package/scripts/dogfood.js +16 -0
  102. package/scripts/hooks/opencode-hook-adapter.js +123 -0
  103. package/scripts/hooks/opencode-telemetry-hook.js +101 -0
  104. package/scripts/hooks/pi-hook-adapter.js +123 -0
  105. package/scripts/hooks/pi-telemetry-hook.js +105 -0
  106. package/scripts/hooks/run-hook.js +8 -0
  107. package/scripts/hooks/utterance-check.js +124 -22
  108. package/scripts/telemetry/lib/config.sh +5 -1
  109. package/src/cli/flow-kit.ts +10 -4
  110. package/src/cli/init.ts +219 -6
  111. package/src/cli/runtime-adapter.ts +10 -5
  112. package/src/cli/telemetry-doctor.ts +4 -1
  113. package/src/cli/utterance-check.ts +71 -1
  114. package/src/runtime-adapters.ts +35 -0
  115. package/src/tools/build-universal-bundles.ts +283 -0
  116. package/src/tools/filter-installed-packs.ts +3 -0
  117. package/src/tools/validate-source-tree.ts +5 -1
@@ -0,0 +1,159 @@
1
+ /**
2
+ * test-steering.ts — Tests for FlowAgentsHooks.steeringContext() kit flow surfacing.
3
+ *
4
+ * Issue #32 AC2: steering context surfaces activated kit flows from the
5
+ * strands-local runtime path (.flow-agents/runtime/strands/flows/).
6
+ *
7
+ * Fixture approach: write fake *.flow.json files (same structure as the real
8
+ * kit flow files produced by activateStrandsLocal) and assert the steering
9
+ * context text contains kit flow ids and descriptions.
10
+ */
11
+
12
+ import { test, describe } from "node:test";
13
+ import assert from "node:assert/strict";
14
+ import fs from "node:fs";
15
+ import os from "node:os";
16
+ import path from "node:path";
17
+ import { FlowAgentsHooks } from "../src/hooks.js";
18
+
19
+ function makeTmpDir(): string {
20
+ return fs.mkdtempSync(path.join(os.tmpdir(), "fa-ts-steering-"));
21
+ }
22
+
23
+ function writeFlow(
24
+ workspace: string,
25
+ kitId: string,
26
+ assetId: string,
27
+ description = ""
28
+ ): void {
29
+ const flowsDir = path.join(
30
+ workspace,
31
+ ".flow-agents",
32
+ "runtime",
33
+ "strands",
34
+ "flows",
35
+ kitId
36
+ );
37
+ fs.mkdirSync(flowsDir, { recursive: true });
38
+ const safeName = assetId.replace(/\./g, "-");
39
+ fs.writeFileSync(
40
+ path.join(flowsDir, `${safeName}.flow.json`),
41
+ JSON.stringify({ id: assetId, description }),
42
+ "utf8"
43
+ );
44
+ }
45
+
46
+ describe("FlowAgentsHooks.steeringContext() — kit flow surfacing (Issue #32 AC2)", () => {
47
+ test("empty string when no runtime strands dir exists", () => {
48
+ const tmpDir = makeTmpDir();
49
+ try {
50
+ const hooks = new FlowAgentsHooks({ workspace: tmpDir });
51
+ const ctx = hooks.steeringContext();
52
+ assert.strictEqual(ctx, "", "Expected empty steering context with no runtime dir");
53
+ } finally {
54
+ fs.rmSync(tmpDir, { recursive: true, force: true });
55
+ }
56
+ });
57
+
58
+ test("KIT FLOWS hint appears when a flow file exists", () => {
59
+ const tmpDir = makeTmpDir();
60
+ try {
61
+ writeFlow(tmpDir, "builder", "builder.shape", "Shape a problem.");
62
+ const hooks = new FlowAgentsHooks({ workspace: tmpDir });
63
+ const ctx = hooks.steeringContext();
64
+ assert.ok(ctx.includes("KIT FLOWS"), `Expected 'KIT FLOWS' in context, got: ${ctx}`);
65
+ } finally {
66
+ fs.rmSync(tmpDir, { recursive: true, force: true });
67
+ }
68
+ });
69
+
70
+ test("flow asset_id appears in steering context", () => {
71
+ const tmpDir = makeTmpDir();
72
+ try {
73
+ writeFlow(tmpDir, "builder", "builder.shape", "Shape a problem.");
74
+ const hooks = new FlowAgentsHooks({ workspace: tmpDir });
75
+ const ctx = hooks.steeringContext();
76
+ assert.ok(
77
+ ctx.includes("builder.shape"),
78
+ `Expected 'builder.shape' in context, got: ${ctx}`
79
+ );
80
+ } finally {
81
+ fs.rmSync(tmpDir, { recursive: true, force: true });
82
+ }
83
+ });
84
+
85
+ test("flow description appears in steering context", () => {
86
+ const tmpDir = makeTmpDir();
87
+ try {
88
+ writeFlow(tmpDir, "builder", "builder.build", "Build a feature end-to-end.");
89
+ const hooks = new FlowAgentsHooks({ workspace: tmpDir });
90
+ const ctx = hooks.steeringContext();
91
+ assert.ok(
92
+ ctx.includes("Build a feature end-to-end."),
93
+ `Expected description in context, got: ${ctx}`
94
+ );
95
+ } finally {
96
+ fs.rmSync(tmpDir, { recursive: true, force: true });
97
+ }
98
+ });
99
+
100
+ test("multiple flows all listed in steering context", () => {
101
+ const tmpDir = makeTmpDir();
102
+ try {
103
+ writeFlow(tmpDir, "builder", "builder.shape", "Shape.");
104
+ writeFlow(tmpDir, "builder", "builder.build", "Build.");
105
+ const hooks = new FlowAgentsHooks({ workspace: tmpDir });
106
+ const ctx = hooks.steeringContext();
107
+ assert.ok(ctx.includes("builder.shape"), "Expected builder.shape in context");
108
+ assert.ok(ctx.includes("builder.build"), "Expected builder.build in context");
109
+ } finally {
110
+ fs.rmSync(tmpDir, { recursive: true, force: true });
111
+ }
112
+ });
113
+
114
+ test("malformed flow JSON does not crash; other flows still listed", () => {
115
+ const tmpDir = makeTmpDir();
116
+ try {
117
+ writeFlow(tmpDir, "builder", "builder.shape", "Shape.");
118
+ // Write a malformed flow file
119
+ const flowsDir = path.join(
120
+ tmpDir,
121
+ ".flow-agents",
122
+ "runtime",
123
+ "strands",
124
+ "flows",
125
+ "builder"
126
+ );
127
+ fs.writeFileSync(path.join(flowsDir, "bad.flow.json"), "{ not valid json", "utf8");
128
+ const hooks = new FlowAgentsHooks({ workspace: tmpDir });
129
+ const ctx = hooks.steeringContext();
130
+ // builder.shape should still appear
131
+ assert.ok(ctx.includes("builder.shape"), "Expected builder.shape despite bad file");
132
+ } finally {
133
+ fs.rmSync(tmpDir, { recursive: true, force: true });
134
+ }
135
+ });
136
+
137
+ test("steering context wraps with --- delimiters", () => {
138
+ const tmpDir = makeTmpDir();
139
+ try {
140
+ writeFlow(tmpDir, "builder", "builder.shape", "Shape.");
141
+ const hooks = new FlowAgentsHooks({ workspace: tmpDir });
142
+ const ctx = hooks.steeringContext();
143
+ assert.ok(ctx.includes("---"), "Expected --- delimiters in context");
144
+ } finally {
145
+ fs.rmSync(tmpDir, { recursive: true, force: true });
146
+ }
147
+ });
148
+
149
+ test("returns string type (even empty)", () => {
150
+ const tmpDir = makeTmpDir();
151
+ try {
152
+ const hooks = new FlowAgentsHooks({ workspace: tmpDir });
153
+ const ctx = hooks.steeringContext();
154
+ assert.strictEqual(typeof ctx, "string");
155
+ } finally {
156
+ fs.rmSync(tmpDir, { recursive: true, force: true });
157
+ }
158
+ });
159
+ });
@@ -0,0 +1,226 @@
1
+ /**
2
+ * test-telemetry.ts — Tests for telemetry module.
3
+ *
4
+ * Covers: event mapping, JSONL emission shape, normalizeToolName.
5
+ * Uses node:test only — no additional dependencies.
6
+ */
7
+
8
+ import { test, describe } from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import fs from "node:fs";
11
+ import os from "node:os";
12
+ import path from "node:path";
13
+ import { TelemetrySink, STRANDS_TO_CANONICAL, normalizeToolName, SCHEMA_VERSION } from "../src/telemetry.js";
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // STRANDS_TO_CANONICAL mapping table
17
+ // ---------------------------------------------------------------------------
18
+
19
+ describe("STRANDS_TO_CANONICAL mapping", () => {
20
+ test("contains all expected Strands TS event class names", () => {
21
+ const expected = new Set([
22
+ "BeforeInvocationEvent",
23
+ "AfterInvocationEvent",
24
+ "BeforeToolCallEvent",
25
+ "AfterToolCallEvent",
26
+ "AgentInitializedEvent",
27
+ "AfterModelCallEvent",
28
+ "MessageAddedEvent",
29
+ ]);
30
+ assert.deepStrictEqual(new Set(Object.keys(STRANDS_TO_CANONICAL)), expected);
31
+ });
32
+
33
+ test("BeforeInvocationEvent → userPromptSubmit", () => {
34
+ assert.strictEqual(STRANDS_TO_CANONICAL.BeforeInvocationEvent, "userPromptSubmit");
35
+ });
36
+
37
+ test("AfterInvocationEvent → stop", () => {
38
+ assert.strictEqual(STRANDS_TO_CANONICAL.AfterInvocationEvent, "stop");
39
+ });
40
+
41
+ test("BeforeToolCallEvent → preToolUse", () => {
42
+ assert.strictEqual(STRANDS_TO_CANONICAL.BeforeToolCallEvent, "preToolUse");
43
+ });
44
+
45
+ test("AfterToolCallEvent → postToolUse", () => {
46
+ assert.strictEqual(STRANDS_TO_CANONICAL.AfterToolCallEvent, "postToolUse");
47
+ });
48
+
49
+ test("AgentInitializedEvent → agentSpawn", () => {
50
+ assert.strictEqual(STRANDS_TO_CANONICAL.AgentInitializedEvent, "agentSpawn");
51
+ });
52
+
53
+ test("all values are non-empty strings", () => {
54
+ for (const [key, value] of Object.entries(STRANDS_TO_CANONICAL)) {
55
+ assert.strictEqual(typeof value, "string", `${key} value must be a string`);
56
+ assert.ok(value.length > 0, `${key} value must be non-empty`);
57
+ }
58
+ });
59
+ });
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // normalizeToolName
63
+ // ---------------------------------------------------------------------------
64
+
65
+ describe("normalizeToolName", () => {
66
+ test("bash → execute_bash", () => {
67
+ assert.strictEqual(normalizeToolName("bash"), "execute_bash");
68
+ });
69
+
70
+ test("edit → fs_write", () => {
71
+ assert.strictEqual(normalizeToolName("edit"), "fs_write");
72
+ });
73
+
74
+ test("write → fs_write", () => {
75
+ assert.strictEqual(normalizeToolName("write"), "fs_write");
76
+ });
77
+
78
+ test("read → fs_read", () => {
79
+ assert.strictEqual(normalizeToolName("read"), "fs_read");
80
+ });
81
+
82
+ test("task → use_subagent", () => {
83
+ assert.strictEqual(normalizeToolName("task"), "use_subagent");
84
+ });
85
+
86
+ test("unknown passthrough", () => {
87
+ assert.strictEqual(normalizeToolName("my_custom_tool"), "my_custom_tool");
88
+ });
89
+ });
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // TelemetrySink emission shape
93
+ // ---------------------------------------------------------------------------
94
+
95
+ function makeTempSink(agentName = "test-agent", runtime = "strands-test"): { sink: TelemetrySink; dir: string } {
96
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fa-ts-telemetry-"));
97
+ const sink = new TelemetrySink({ sinkPath: dir, agentName, runtime });
98
+ return { sink, dir };
99
+ }
100
+
101
+ function readEvents(dir: string): Record<string, unknown>[] {
102
+ const logFile = path.join(dir, "full.jsonl");
103
+ if (!fs.existsSync(logFile)) return [];
104
+ const lines = fs.readFileSync(logFile, "utf8").trim().split("\n");
105
+ return lines.filter((l) => l.trim()).map((l) => JSON.parse(l) as Record<string, unknown>);
106
+ }
107
+
108
+ describe("TelemetrySink emission shape", () => {
109
+ test("session start event has correct schema_version and event_type", () => {
110
+ const { sink, dir } = makeTempSink();
111
+ const evt = sink.emitSessionStart();
112
+ assert.strictEqual(evt.schema_version, SCHEMA_VERSION);
113
+ assert.strictEqual(evt.event_type, "session.start");
114
+ // cleanup
115
+ fs.rmSync(dir, { recursive: true, force: true });
116
+ });
117
+
118
+ test("session start written to JSONL", () => {
119
+ const { sink, dir } = makeTempSink();
120
+ sink.emitSessionStart();
121
+ const events = readEvents(dir);
122
+ assert.strictEqual(events.length, 1);
123
+ assert.strictEqual((events[0] as Record<string, unknown>).event_type, "session.start");
124
+ fs.rmSync(dir, { recursive: true, force: true });
125
+ });
126
+
127
+ test("event has required top-level fields (timestamp, session_id, event_id)", () => {
128
+ const { sink, dir } = makeTempSink();
129
+ const evt = sink.emitSessionStart();
130
+ assert.ok("timestamp" in evt, "missing timestamp");
131
+ assert.ok("session_id" in evt, "missing session_id");
132
+ assert.ok("event_id" in evt, "missing event_id");
133
+ assert.ok("agent" in evt, "missing agent");
134
+ assert.ok("hook" in evt, "missing hook");
135
+ fs.rmSync(dir, { recursive: true, force: true });
136
+ });
137
+
138
+ test("agent sub-object has name and runtime", () => {
139
+ const { sink, dir } = makeTempSink("my-agent", "strands-ts");
140
+ const evt = sink.emitSessionStart();
141
+ const agent = evt.agent as Record<string, unknown>;
142
+ assert.strictEqual(agent.name, "my-agent");
143
+ assert.strictEqual(agent.runtime, "strands-ts");
144
+ fs.rmSync(dir, { recursive: true, force: true });
145
+ });
146
+
147
+ test("hook sub-object has event_name and source fields", () => {
148
+ const { sink, dir } = makeTempSink();
149
+ const evt = sink.emitSessionStart();
150
+ const hook = evt.hook as Record<string, unknown>;
151
+ assert.ok("event_name" in hook, "hook.event_name missing");
152
+ assert.ok("source" in hook, "hook.source missing");
153
+ assert.strictEqual(hook.source, "strands-ts");
154
+ fs.rmSync(dir, { recursive: true, force: true });
155
+ });
156
+
157
+ test("tool invoke event has correct event_type and tool fields", () => {
158
+ const { sink, dir } = makeTempSink();
159
+ const evt = sink.emitToolInvoke("edit", { path: "foo.py" });
160
+ assert.strictEqual(evt.event_type, "tool.invoke");
161
+ const tool = evt.tool as Record<string, unknown>;
162
+ assert.strictEqual(tool.name, "edit");
163
+ assert.strictEqual(tool.normalized_name, "fs_write");
164
+ assert.deepStrictEqual(tool.input, { path: "foo.py" });
165
+ fs.rmSync(dir, { recursive: true, force: true });
166
+ });
167
+
168
+ test("tool result event has correct event_type", () => {
169
+ const { sink, dir } = makeTempSink();
170
+ const evt = sink.emitToolResult("read", "file contents");
171
+ assert.strictEqual(evt.event_type, "tool.result");
172
+ const tool = evt.tool as Record<string, unknown>;
173
+ assert.strictEqual(tool.name, "read");
174
+ assert.strictEqual(tool.normalized_name, "fs_read");
175
+ assert.strictEqual(tool.output, "file contents");
176
+ fs.rmSync(dir, { recursive: true, force: true });
177
+ });
178
+
179
+ test("session end has event_type session.end", () => {
180
+ const { sink, dir } = makeTempSink();
181
+ const evt = sink.emitSessionEnd(1000);
182
+ assert.strictEqual(evt.event_type, "session.end");
183
+ fs.rmSync(dir, { recursive: true, force: true });
184
+ });
185
+
186
+ test("user prompt submit has event_type turn.user", () => {
187
+ const { sink, dir } = makeTempSink();
188
+ const evt = sink.emitUserPromptSubmit();
189
+ assert.strictEqual(evt.event_type, "turn.user");
190
+ fs.rmSync(dir, { recursive: true, force: true });
191
+ });
192
+
193
+ test("multiple events share the same session_id", () => {
194
+ const { sink, dir } = makeTempSink();
195
+ sink.emitSessionStart();
196
+ sink.emitToolInvoke("read", {});
197
+ sink.emitSessionEnd();
198
+ const events = readEvents(dir);
199
+ const sessionIds = new Set(events.map((e) => (e as Record<string, unknown>).session_id));
200
+ assert.strictEqual(sessionIds.size, 1, "All events must share one session_id");
201
+ fs.rmSync(dir, { recursive: true, force: true });
202
+ });
203
+
204
+ test("each JSONL line is valid JSON", () => {
205
+ const { sink, dir } = makeTempSink();
206
+ sink.emitSessionStart();
207
+ sink.emitToolInvoke("bash", { command: "ls" });
208
+ sink.emitSessionEnd(500);
209
+ const logFile = path.join(dir, "full.jsonl");
210
+ const lines = fs.readFileSync(logFile, "utf8").split("\n");
211
+ for (const line of lines) {
212
+ if (!line.trim()) continue;
213
+ const parsed = JSON.parse(line); // throws on invalid JSON
214
+ assert.strictEqual(typeof parsed, "object");
215
+ }
216
+ fs.rmSync(dir, { recursive: true, force: true });
217
+ });
218
+
219
+ test("sinkPath directory creates full.jsonl", () => {
220
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fa-ts-sink-dir-"));
221
+ const sink = new TelemetrySink({ sinkPath: dir });
222
+ sink.emitSessionStart();
223
+ assert.ok(fs.existsSync(path.join(dir, "full.jsonl")));
224
+ fs.rmSync(dir, { recursive: true, force: true });
225
+ });
226
+ });
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "ignoreDeprecations": "6.0",
7
+ "lib": ["ES2022"],
8
+ "types": ["node"],
9
+ "rootDir": ".",
10
+ "outDir": "dist",
11
+ "strict": true,
12
+ "noUnusedLocals": true,
13
+ "noUnusedParameters": true,
14
+ "esModuleInterop": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "skipLibCheck": true
17
+ },
18
+ "include": ["src/**/*.ts", "test/**/*.ts"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }
package/kits/catalog.json CHANGED
@@ -6,6 +6,12 @@
6
6
  "name": "Builder Kit",
7
7
  "path": "kits/builder",
8
8
  "description": "Flow-backed shaping, planning, build, verification, merge readiness, pull request readiness, and learning workflows."
9
+ },
10
+ {
11
+ "id": "knowledge",
12
+ "name": "Knowledge Kit",
13
+ "path": "kits/knowledge",
14
+ "description": "Store contract with record types (raw/compiled/concept), mutation operations with required provenance, default markdown+frontmatter+wikilink+graph-index adapter, and a parameterized contract test suite."
9
15
  }
10
16
  ]
11
17
  }