@kontourai/flow-agents 0.1.1 → 0.2.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/.github/dependabot.yml +23 -0
- package/.github/workflows/publish-npm.yml +1 -1
- package/.github/workflows/release-please.yml +31 -0
- package/.github/workflows/runtime-compat.yml +118 -0
- package/CHANGELOG.md +38 -0
- package/CONTRIBUTING.md +4 -0
- package/README.md +58 -19
- package/build/src/cli/init.js +215 -5
- package/build/src/cli/utterance-check.js +236 -0
- package/build/src/cli.js +3 -0
- package/build/src/tools/build-universal-bundles.js +268 -0
- package/build/src/tools/filter-installed-packs.js +3 -0
- package/build/src/tools/validate-source-tree.js +6 -1
- package/context/scripts/telemetry/lib/config.sh +5 -1
- package/context/settings/flow-agents-settings.json +7 -0
- package/docs/agent-system-guidebook.md +4 -5
- package/docs/context-map.md +1 -0
- package/docs/index.md +46 -6
- package/docs/integrations/conformance.md +246 -0
- package/docs/integrations/framework-adapter.md +275 -0
- package/docs/integrations/harness-install.md +213 -0
- package/docs/integrations/index.md +54 -0
- package/docs/north-star.md +3 -3
- package/docs/repository-structure.md +1 -1
- package/docs/skills-map.md +10 -4
- package/docs/spec/runtime-hook-surface.md +472 -0
- package/docs/survey-utterance-check.md +308 -0
- package/docs/vision.md +45 -0
- package/docs/workflow-usage-guide.md +1 -1
- package/evals/acceptance/run.sh +4 -2
- package/evals/acceptance/test_opencode_harness.sh +121 -0
- package/evals/acceptance/test_pi_harness.sh +98 -0
- package/evals/integration/test_bundle_install.sh +226 -1
- package/evals/integration/test_bundle_lifecycle.sh +641 -0
- package/evals/integration/test_utterance_check.sh +518 -0
- package/evals/run.sh +2 -0
- package/evals/static/test_universal_bundles.sh +137 -2
- package/integrations/strands/README.md +256 -0
- package/integrations/strands/example.py +74 -0
- package/integrations/strands/flow_agents_strands/__init__.py +27 -0
- package/integrations/strands/flow_agents_strands/hooks.py +194 -0
- package/integrations/strands/flow_agents_strands/policy.py +348 -0
- package/integrations/strands/flow_agents_strands/steering.py +172 -0
- package/integrations/strands/flow_agents_strands/telemetry.py +238 -0
- package/integrations/strands/pyproject.toml +38 -0
- package/integrations/strands/tests/__init__.py +0 -0
- package/integrations/strands/tests/test_hooks.py +304 -0
- package/integrations/strands/tests/test_policy.py +315 -0
- package/integrations/strands/tests/test_telemetry.py +184 -0
- package/integrations/strands-ts/README.md +224 -0
- package/integrations/strands-ts/bin/conformance-shim.mjs +257 -0
- package/integrations/strands-ts/package.json +53 -0
- package/integrations/strands-ts/src/hooks.ts +208 -0
- package/integrations/strands-ts/src/index.ts +22 -0
- package/integrations/strands-ts/src/policy.ts +345 -0
- package/integrations/strands-ts/src/telemetry.ts +251 -0
- package/integrations/strands-ts/test/test-policy.ts +322 -0
- package/integrations/strands-ts/test/test-telemetry.ts +226 -0
- package/integrations/strands-ts/tsconfig.json +20 -0
- package/package.json +7 -2
- package/packaging/conformance/README.md +142 -0
- package/packaging/conformance/fixtures/config-protection--allow-no-path.json +18 -0
- package/packaging/conformance/fixtures/config-protection--allow-safe-file.json +20 -0
- package/packaging/conformance/fixtures/config-protection--block-biome.json +20 -0
- package/packaging/conformance/fixtures/config-protection--block-eslintrc.json +20 -0
- package/packaging/conformance/fixtures/quality-gate--allow-no-path.json +17 -0
- package/packaging/conformance/fixtures/quality-gate--allow-nonexistent-file.json +19 -0
- package/packaging/conformance/fixtures/stop-goal-fit--allow-clean-cwd.json +17 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-strict-mode.json +23 -0
- package/packaging/conformance/fixtures/stop-goal-fit--warn-active-delivery.json +21 -0
- package/packaging/conformance/fixtures/workflow-steering--allow-no-state.json +16 -0
- package/packaging/conformance/fixtures/workflow-steering--inject-active-state.json +29 -0
- package/packaging/conformance/fixtures/workflow-steering--inject-subagent-steering.json +25 -0
- package/packaging/conformance/package.json +4 -0
- package/packaging/conformance/run-conformance.js +322 -0
- package/packaging/manifest.json +59 -0
- package/schemas/flow-agents-settings.schema.json +48 -0
- package/scripts/README.md +5 -0
- package/scripts/dogfood.js +16 -0
- package/scripts/hooks/opencode-hook-adapter.js +123 -0
- package/scripts/hooks/opencode-telemetry-hook.js +101 -0
- package/scripts/hooks/pi-hook-adapter.js +123 -0
- package/scripts/hooks/pi-telemetry-hook.js +105 -0
- package/scripts/hooks/run-hook.js +8 -0
- package/scripts/hooks/utterance-check.js +327 -0
- package/scripts/telemetry/lib/config.sh +5 -1
- package/skills/idea-to-backlog/SKILL.md +1 -1
- package/src/cli/init.ts +219 -6
- package/src/cli/utterance-check.ts +324 -0
- package/src/cli.ts +3 -0
- package/src/tools/build-universal-bundles.ts +266 -0
- package/src/tools/filter-installed-packs.ts +3 -0
- package/src/tools/validate-source-tree.ts +6 -1
- package/build/src/cli/docs-preview.js +0 -39
- package/build/src/cli/export-bookmarks.js +0 -38
- package/build/src/cli/import-bookmarks.js +0 -50
- package/build/src/cli/instinct-cli.js +0 -93
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { flagBool, flagString, parseArgs } from "../lib/args.js";
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Helpers
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
function usage() {
|
|
8
|
+
console.error([
|
|
9
|
+
"usage: flow-agents utterance-check check [options]",
|
|
10
|
+
"",
|
|
11
|
+
"Check an agent utterance for evidence coverage using @kontourai/survey.",
|
|
12
|
+
"Requires @kontourai/survey to be installed in the target workspace.",
|
|
13
|
+
"",
|
|
14
|
+
"Options:",
|
|
15
|
+
" --utterance TEXT Utterance text to check (required unless --not-configured).",
|
|
16
|
+
" --bundle-path FILE Trust bundle JSON file. Omit for an empty bundle (all unsupported).",
|
|
17
|
+
" --agent-id ID Agent identifier for provenance (default: flow-agents-utterance-check).",
|
|
18
|
+
" --extractor NAME Extractor to use: 'reference' (default, pattern-based) or 'anthropic'",
|
|
19
|
+
" (model-backed, requires ANTHROPIC_API_KEY and @kontourai/survey/anthropic).",
|
|
20
|
+
" --model MODEL Model for the anthropic extractor (e.g. claude-haiku-4-5).",
|
|
21
|
+
" --not-configured Skip survey call; output not_configured without error.",
|
|
22
|
+
" --strict Exit non-zero when any badge is disputed, rejected, or unsupported.",
|
|
23
|
+
" --help Show this help.",
|
|
24
|
+
].join("\n"));
|
|
25
|
+
}
|
|
26
|
+
function excerptText(text, maxLen = 200) {
|
|
27
|
+
const trimmed = text.trim().replace(/\s+/g, " ");
|
|
28
|
+
return trimmed.length > maxLen ? `${trimmed.slice(0, maxLen - 3)}...` : trimmed;
|
|
29
|
+
}
|
|
30
|
+
function badgeSummary(statements) {
|
|
31
|
+
if (statements.length === 0)
|
|
32
|
+
return "no factual statements extracted";
|
|
33
|
+
const counts = {};
|
|
34
|
+
for (const s of statements) {
|
|
35
|
+
counts[s.badge] = (counts[s.badge] ?? 0) + 1;
|
|
36
|
+
}
|
|
37
|
+
return Object.entries(counts)
|
|
38
|
+
.sort((a, b) => b[1] - a[1])
|
|
39
|
+
.map(([badge, n]) => `${badge}:${n}`)
|
|
40
|
+
.join(", ");
|
|
41
|
+
}
|
|
42
|
+
function hasConcerningBadge(badge) {
|
|
43
|
+
return badge === "disputed" || badge === "rejected" || badge === "unsupported";
|
|
44
|
+
}
|
|
45
|
+
async function loadSurvey() {
|
|
46
|
+
try {
|
|
47
|
+
const pkg = "@kontourai/survey";
|
|
48
|
+
// Dynamic import avoids a static dependency on @kontourai/survey —
|
|
49
|
+
// the same pattern survey/src/anthropic.ts uses for @anthropic-ai/sdk.
|
|
50
|
+
const mod = await Function("m", "return import(m)")(pkg);
|
|
51
|
+
return mod;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Dynamically import @kontourai/survey/anthropic and create the Anthropic extractor.
|
|
59
|
+
* Fails open with a clear not_configured message when the key or peer dep is missing.
|
|
60
|
+
*/
|
|
61
|
+
async function loadAnthropicExtractor(model) {
|
|
62
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
63
|
+
if (!apiKey) {
|
|
64
|
+
return {
|
|
65
|
+
notConfigured: true,
|
|
66
|
+
reason: "anthropic extractor requires ANTHROPIC_API_KEY to be set. " +
|
|
67
|
+
"Set the environment variable or switch extractor to 'reference'.",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const pkg = "@kontourai/survey/anthropic";
|
|
72
|
+
const mod = await Function("m", "return import(m)")(pkg);
|
|
73
|
+
if (typeof mod.createAnthropicUtteranceExtractor !== "function") {
|
|
74
|
+
return {
|
|
75
|
+
notConfigured: true,
|
|
76
|
+
reason: "@kontourai/survey/anthropic does not export createAnthropicUtteranceExtractor. " +
|
|
77
|
+
"Update @kontourai/survey to a version that supports the anthropic extractor.",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const opts = { apiKey };
|
|
81
|
+
if (model)
|
|
82
|
+
opts.model = model;
|
|
83
|
+
return mod.createAnthropicUtteranceExtractor(opts);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
87
|
+
return {
|
|
88
|
+
notConfigured: true,
|
|
89
|
+
reason: `@kontourai/survey/anthropic is not available: ${msg}. ` +
|
|
90
|
+
"Install @kontourai/survey with the anthropic subpath export, or switch extractor to 'reference'.",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Core check logic
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
async function runCheck(argv) {
|
|
98
|
+
const { flags } = parseArgs(argv);
|
|
99
|
+
if (flagBool(flags, "help")) {
|
|
100
|
+
usage();
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
const agentId = flagString(flags, "agent-id") ?? "flow-agents-utterance-check";
|
|
104
|
+
const notConfigured = flagBool(flags, "not-configured");
|
|
105
|
+
const strict = flagBool(flags, "strict");
|
|
106
|
+
const extractorName = flagString(flags, "extractor") ?? "reference";
|
|
107
|
+
const model = flagString(flags, "model");
|
|
108
|
+
if (notConfigured) {
|
|
109
|
+
const report = {
|
|
110
|
+
status: "not_configured",
|
|
111
|
+
agent_id: agentId,
|
|
112
|
+
utterance_excerpt: "",
|
|
113
|
+
statements: [],
|
|
114
|
+
summary: "@kontourai/survey is not configured for this workspace.",
|
|
115
|
+
};
|
|
116
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
const utterance = flagString(flags, "utterance");
|
|
120
|
+
if (!utterance) {
|
|
121
|
+
usage();
|
|
122
|
+
return 3;
|
|
123
|
+
}
|
|
124
|
+
const bundlePath = flagString(flags, "bundle-path");
|
|
125
|
+
let bundle = { claims: [] };
|
|
126
|
+
if (bundlePath) {
|
|
127
|
+
const resolved = path.resolve(bundlePath);
|
|
128
|
+
try {
|
|
129
|
+
const raw = fs.readFileSync(resolved, "utf8");
|
|
130
|
+
bundle = JSON.parse(raw);
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
134
|
+
process.stderr.write(`[UtteranceCheck] could not read bundle from ${resolved}: ${msg}\n`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const survey = await loadSurvey();
|
|
138
|
+
if (!survey) {
|
|
139
|
+
const report = {
|
|
140
|
+
status: "not_configured",
|
|
141
|
+
agent_id: agentId,
|
|
142
|
+
utterance_excerpt: excerptText(utterance),
|
|
143
|
+
statements: [],
|
|
144
|
+
summary: "@kontourai/survey is not installed. Install it or run with --not-configured.",
|
|
145
|
+
};
|
|
146
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
147
|
+
process.stderr.write("[UtteranceCheck] not_configured: @kontourai/survey is not installed in this workspace.\n");
|
|
148
|
+
return 1;
|
|
149
|
+
}
|
|
150
|
+
const { surveyAgentUtterance, referenceUtteranceExtractor } = survey;
|
|
151
|
+
// Resolve which extractor to use.
|
|
152
|
+
let extractor;
|
|
153
|
+
if (extractorName === "anthropic") {
|
|
154
|
+
const anthropicResult = await loadAnthropicExtractor(model);
|
|
155
|
+
if ("notConfigured" in anthropicResult) {
|
|
156
|
+
// Fail open: emit not_configured with a clear reason rather than erroring.
|
|
157
|
+
const report = {
|
|
158
|
+
status: "not_configured",
|
|
159
|
+
agent_id: agentId,
|
|
160
|
+
utterance_excerpt: excerptText(utterance),
|
|
161
|
+
statements: [],
|
|
162
|
+
summary: anthropicResult.reason,
|
|
163
|
+
};
|
|
164
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
165
|
+
process.stderr.write(`[UtteranceCheck] not_configured: ${anthropicResult.reason}\n`);
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
extractor = anthropicResult;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
extractor = referenceUtteranceExtractor;
|
|
172
|
+
}
|
|
173
|
+
let trustReport;
|
|
174
|
+
try {
|
|
175
|
+
trustReport = await surveyAgentUtterance(utterance, extractor, {
|
|
176
|
+
bundle,
|
|
177
|
+
agentId,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
182
|
+
const report = {
|
|
183
|
+
status: "error",
|
|
184
|
+
agent_id: agentId,
|
|
185
|
+
utterance_excerpt: excerptText(utterance),
|
|
186
|
+
statements: [],
|
|
187
|
+
summary: `Survey call failed: ${msg}`,
|
|
188
|
+
};
|
|
189
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
190
|
+
process.stderr.write(`[UtteranceCheck] survey call failed: ${msg}\n`);
|
|
191
|
+
return 1;
|
|
192
|
+
}
|
|
193
|
+
const statements = trustReport.statements.map((s) => ({
|
|
194
|
+
excerpt: s.excerpt,
|
|
195
|
+
badge: s.badge,
|
|
196
|
+
target: s.target,
|
|
197
|
+
span: s.span,
|
|
198
|
+
}));
|
|
199
|
+
const summary = badgeSummary(statements);
|
|
200
|
+
const report = {
|
|
201
|
+
status: "ok",
|
|
202
|
+
agent_id: agentId,
|
|
203
|
+
utterance_excerpt: excerptText(utterance),
|
|
204
|
+
statements,
|
|
205
|
+
summary,
|
|
206
|
+
};
|
|
207
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
208
|
+
const concerning = statements.filter((s) => hasConcerningBadge(s.badge));
|
|
209
|
+
if (concerning.length > 0) {
|
|
210
|
+
process.stderr.write(`[UtteranceCheck] ${concerning.length} statement(s) lack evidence coverage: ${summary}\n`);
|
|
211
|
+
for (const s of concerning.slice(0, 4)) {
|
|
212
|
+
process.stderr.write(` - [${s.badge}] "${excerptText(s.excerpt, 100)}"\n`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (strict && concerning.length > 0)
|
|
216
|
+
return 2;
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// Entry point
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
223
|
+
const [subcommand, ...rest] = argv;
|
|
224
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
225
|
+
usage();
|
|
226
|
+
return 0;
|
|
227
|
+
}
|
|
228
|
+
if (subcommand !== "check") {
|
|
229
|
+
console.error(`Unknown utterance-check subcommand: ${subcommand}`);
|
|
230
|
+
usage();
|
|
231
|
+
return 3;
|
|
232
|
+
}
|
|
233
|
+
return runCheck(rest);
|
|
234
|
+
}
|
|
235
|
+
if (import.meta.url === `file://${process.argv[1]}`)
|
|
236
|
+
process.exit(await main());
|
package/build/src/cli.js
CHANGED
|
@@ -19,6 +19,7 @@ import { main as validateSource } from "./tools/validate-source-tree.js";
|
|
|
19
19
|
import { main as validatePackage } from "./tools/validate-package.js";
|
|
20
20
|
import { main as validateHookInfluence } from "./cli/validate-hook-influence.js";
|
|
21
21
|
import { main as runtimeAdapter } from "./cli/runtime-adapter.js";
|
|
22
|
+
import { main as utteranceCheck } from "./cli/utterance-check.js";
|
|
22
23
|
const availableCommands = new Map([
|
|
23
24
|
["build-bundles", () => buildBundles()],
|
|
24
25
|
["console-learning-projection", consoleLearningProjection],
|
|
@@ -32,6 +33,7 @@ const availableCommands = new Map([
|
|
|
32
33
|
["publish-change", publishChange],
|
|
33
34
|
["pull-work-provider", pullWorkProvider],
|
|
34
35
|
["runtime-adapter", runtimeAdapter],
|
|
36
|
+
["utterance-check", utteranceCheck],
|
|
35
37
|
["telemetry-doctor", telemetryDoctor],
|
|
36
38
|
["usage-feedback", usageFeedback],
|
|
37
39
|
["veritas-governance", veritasGovernance],
|
|
@@ -56,6 +58,7 @@ const aliases = new Map([
|
|
|
56
58
|
["flow-agents-usage-feedback", "usage-feedback"],
|
|
57
59
|
["flow-agents-veritas-governance", "veritas-governance"],
|
|
58
60
|
["flow-agents-validate-hook-influence", "validate-hook-influence"],
|
|
61
|
+
["flow-agents-utterance-check", "utterance-check"],
|
|
59
62
|
["flow-agents-validate-source", "validate-source"],
|
|
60
63
|
["flow-agents-workflow-artifact-cleanup-audit", "workflow-artifact-cleanup-audit"],
|
|
61
64
|
]);
|
|
@@ -361,6 +361,270 @@ function buildCodex(agents) {
|
|
|
361
361
|
writeText(path.join(bundle, "install.sh"), installScript("Codex", "/path/to/workspace"));
|
|
362
362
|
fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
|
|
363
363
|
}
|
|
364
|
+
function exportOpencodeAgent(spec) {
|
|
365
|
+
// Determine agent mode: orchestrator-like agents -> primary, others -> subagent
|
|
366
|
+
const primaryAgents = new Set(["dev"]);
|
|
367
|
+
const mode = primaryAgents.has(spec.name) ? "primary" : "subagent";
|
|
368
|
+
const prompt = appendExportNote(sanitizeText(spec.prompt, "opencode", "<bundle-root>"), "Kiro hook wiring and JSON-only runtime fields were omitted. If this agent mentions Kiro-specific scheduler or hook behavior, treat that as optional operational guidance rather than a hard dependency.");
|
|
369
|
+
const lines = ["---"];
|
|
370
|
+
lines.push(`description: ${String(spec.description ?? "").trim()}`);
|
|
371
|
+
lines.push(`mode: ${mode}`);
|
|
372
|
+
lines.push(`model: ${mapped("claude_model_map", spec.model)}`);
|
|
373
|
+
lines.push("---");
|
|
374
|
+
lines.push("");
|
|
375
|
+
lines.push(prompt);
|
|
376
|
+
return lines.join("\n");
|
|
377
|
+
}
|
|
378
|
+
function exportOpencodePlugin() {
|
|
379
|
+
// Generate the Flow Agents opencode plugin.
|
|
380
|
+
// opencode plugins are auto-loaded from .opencode/plugins/*.js at startup.
|
|
381
|
+
//
|
|
382
|
+
// NOTE: opencode has no direct user-prompt-submit hook. For prompt-submit
|
|
383
|
+
// workflow steering, we wire the steering command behind session.created
|
|
384
|
+
// (for session-start steering context) and tool.execute.before (for
|
|
385
|
+
// policy). This is the closest reasonable approximation — documented here
|
|
386
|
+
// as an honest gap matching the codex live-hook-influence caveat pattern.
|
|
387
|
+
return `/**
|
|
388
|
+
* Flow Agents opencode plugin.
|
|
389
|
+
*
|
|
390
|
+
* Auto-loaded from .opencode/plugins/flow-agents.js at opencode startup.
|
|
391
|
+
* Delegates policy and telemetry decisions to shared scripts in scripts/hooks/
|
|
392
|
+
* using the same payload contract as the claude/codex adapters.
|
|
393
|
+
*
|
|
394
|
+
* EVENT MAPPING NOTE: opencode has no direct user-prompt-submit hook.
|
|
395
|
+
* Workflow steering (workflow-steering.js) is wired to session.created
|
|
396
|
+
* (session-start context) and tool.execute.before (per-tool policy).
|
|
397
|
+
* This approximates the UserPromptSubmit behavior in other runtimes but
|
|
398
|
+
* cannot intercept mid-session user messages before they are processed.
|
|
399
|
+
* This is an accepted gap documented here analogously to the codex
|
|
400
|
+
* live-hook-influence caveat.
|
|
401
|
+
*/
|
|
402
|
+
|
|
403
|
+
import { spawnSync } from 'node:child_process';
|
|
404
|
+
import { join, basename } from 'node:path';
|
|
405
|
+
|
|
406
|
+
// opencode runs plugins inside its own compiled (Bun-based) binary, so
|
|
407
|
+
// process.execPath points at opencode itself — spawning it with a script
|
|
408
|
+
// path silently does nothing (caught by live acceptance smoke 2026-06-11).
|
|
409
|
+
// Resolve a real node binary instead; fall back to PATH lookup.
|
|
410
|
+
const NODE_BIN = basename(process.execPath).startsWith('node') ? process.execPath : 'node';
|
|
411
|
+
|
|
412
|
+
export const FlowAgentsPlugin = async ({ project, client, $, directory, worktree }) => {
|
|
413
|
+
const root = directory || process.cwd();
|
|
414
|
+
|
|
415
|
+
// The hook scripts read the event payload from stdin; an empty stdin makes
|
|
416
|
+
// the telemetry pipeline silently skip the emit (fail-open), so every spawn
|
|
417
|
+
// must pass a payload (caught by live acceptance smoke 2026-06-11).
|
|
418
|
+
function hookPayload(eventName, detail) {
|
|
419
|
+
return JSON.stringify({ hook_event_name: eventName, cwd: root, ...(detail || {}) });
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function runAdapter(adapterScript, eventName, detail, ...args) {
|
|
423
|
+
const adapterPath = join(root, 'scripts', 'hooks', adapterScript);
|
|
424
|
+
const result = spawnSync(NODE_BIN, [adapterPath, eventName, ...args], {
|
|
425
|
+
input: hookPayload(eventName, detail),
|
|
426
|
+
encoding: 'utf8',
|
|
427
|
+
cwd: root,
|
|
428
|
+
env: { ...process.env, FLOW_AGENTS_HOOK_RUNTIME: 'opencode' },
|
|
429
|
+
timeout: 30000,
|
|
430
|
+
});
|
|
431
|
+
try {
|
|
432
|
+
return JSON.parse(result.stdout || '{}');
|
|
433
|
+
} catch {
|
|
434
|
+
return { allow: true };
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function runTelemetry(eventName, detail) {
|
|
439
|
+
const telemetryPath = join(root, 'scripts', 'hooks', 'opencode-telemetry-hook.js');
|
|
440
|
+
spawnSync(NODE_BIN, [telemetryPath, eventName, 'dev'], {
|
|
441
|
+
input: hookPayload(eventName, detail),
|
|
442
|
+
encoding: 'utf8',
|
|
443
|
+
cwd: root,
|
|
444
|
+
env: { ...process.env, FLOW_AGENTS_TELEMETRY_RUNTIME: 'opencode' },
|
|
445
|
+
timeout: 10000,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return {
|
|
450
|
+
'session.created': async (_input, _output) => {
|
|
451
|
+
runTelemetry('session.created');
|
|
452
|
+
// Wire workflow steering on session start for context injection
|
|
453
|
+
runAdapter('opencode-hook-adapter.js', 'session.created', null, 'workflow-steering', 'workflow-steering.js', 'default');
|
|
454
|
+
},
|
|
455
|
+
'tool.execute.before': async (input, output) => {
|
|
456
|
+
const detail = { tool: input && input.tool, args: output && output.args };
|
|
457
|
+
runTelemetry('tool.execute.before', detail);
|
|
458
|
+
const policyResult = runAdapter('opencode-hook-adapter.js', 'tool.execute.before', detail, 'config-protection', 'config-protection.js', 'default');
|
|
459
|
+
if (policyResult && policyResult.allow === false) {
|
|
460
|
+
throw new Error(policyResult.reason || 'Blocked by Flow Agents hook policy.');
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
'tool.execute.after': async (input, output) => {
|
|
464
|
+
const detail = { tool: input && input.tool };
|
|
465
|
+
runTelemetry('tool.execute.after', detail);
|
|
466
|
+
runAdapter('opencode-hook-adapter.js', 'tool.execute.after', detail, 'quality-gate', 'quality-gate.js', 'default');
|
|
467
|
+
},
|
|
468
|
+
'session.idle': async (_input, _output) => {
|
|
469
|
+
runTelemetry('session.idle');
|
|
470
|
+
runAdapter('opencode-hook-adapter.js', 'session.idle', null, 'stop-goal-fit', 'stop-goal-fit.js', 'default');
|
|
471
|
+
},
|
|
472
|
+
'session.error': async (_input, _output) => {
|
|
473
|
+
runTelemetry('session.error');
|
|
474
|
+
},
|
|
475
|
+
'session.compacted': async (_input, _output) => {
|
|
476
|
+
runTelemetry('session.compacted');
|
|
477
|
+
},
|
|
478
|
+
'permission.asked': async (_input, _output) => {
|
|
479
|
+
runTelemetry('permission.asked');
|
|
480
|
+
},
|
|
481
|
+
'file.edited': async (_input, _output) => {
|
|
482
|
+
runTelemetry('file.edited');
|
|
483
|
+
},
|
|
484
|
+
};
|
|
485
|
+
};
|
|
486
|
+
`;
|
|
487
|
+
}
|
|
488
|
+
function exportOpencodeConfig() {
|
|
489
|
+
// opencode's config schema requires `instructions` to be an ARRAY of
|
|
490
|
+
// instruction file paths/globs (a bare string fails validation and aborts
|
|
491
|
+
// startup). AGENTS.md is loaded natively by opencode, so the config stays
|
|
492
|
+
// minimal rather than double-including it.
|
|
493
|
+
return `${JSON.stringify({
|
|
494
|
+
$schema: "https://opencode.ai/config.json",
|
|
495
|
+
}, null, 2)}\n`;
|
|
496
|
+
}
|
|
497
|
+
function buildOpencode(agents) {
|
|
498
|
+
const bundle = path.join(dist, "opencode");
|
|
499
|
+
resetDir(bundle);
|
|
500
|
+
copySharedContent(bundle, "opencode", "<bundle-root>");
|
|
501
|
+
writeText(path.join(bundle, manifest.opencode.task_dir, ".gitkeep"), "");
|
|
502
|
+
for (const spec of agents) {
|
|
503
|
+
writeText(path.join(bundle, ".opencode/agents", `${spec.name}.md`), exportOpencodeAgent(spec));
|
|
504
|
+
}
|
|
505
|
+
for (const skill of fs.readdirSync(path.join(root, "skills"))) {
|
|
506
|
+
const skillPath = path.join(root, "skills", skill, "SKILL.md");
|
|
507
|
+
if (fs.existsSync(skillPath))
|
|
508
|
+
writeText(path.join(bundle, ".opencode/skills", skill, "SKILL.md"), sanitizeText(readText(skillPath), "opencode", "<bundle-root>"));
|
|
509
|
+
}
|
|
510
|
+
writeText(path.join(bundle, ".opencode/plugins/flow-agents.js"), exportOpencodePlugin());
|
|
511
|
+
writeText(path.join(bundle, "opencode.json"), exportOpencodeConfig());
|
|
512
|
+
writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("opencode", agents, manifest.opencode.task_dir));
|
|
513
|
+
writeText(path.join(bundle, "README.md"), exportTargetReadme("opencode", "bash install.sh /path/to/workspace"));
|
|
514
|
+
writeText(path.join(bundle, "install.sh"), installScript("opencode", "/path/to/workspace"));
|
|
515
|
+
fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
|
|
516
|
+
}
|
|
517
|
+
function exportPiExtension() {
|
|
518
|
+
// Generate the Flow Agents pi extension.
|
|
519
|
+
// pi extensions are auto-discovered from .pi/extensions/*.ts (needs project trust).
|
|
520
|
+
// pi has no named-subagent registry; agents are not exported. The extension
|
|
521
|
+
// provides workflow steering (via before_agent_start context injection),
|
|
522
|
+
// tool-call policy (via tool_call event), and telemetry delegation to shared scripts.
|
|
523
|
+
return `/**
|
|
524
|
+
* Flow Agents pi extension.
|
|
525
|
+
*
|
|
526
|
+
* Auto-discovered from .pi/extensions/flow-agents.ts at startup (needs project trust).
|
|
527
|
+
* Delegates policy and telemetry to shared scripts/hooks/ using spawnSync,
|
|
528
|
+
* mirroring the payload contract used by the claude/codex adapters.
|
|
529
|
+
*
|
|
530
|
+
* NOTE: pi has no named-subagent registry. Agents are not exported for pi.
|
|
531
|
+
* Rely on AGENTS.md + skills + this extension for workflow guidance.
|
|
532
|
+
*/
|
|
533
|
+
|
|
534
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
535
|
+
import { spawnSync } from "node:child_process";
|
|
536
|
+
import { join, basename } from "node:path";
|
|
537
|
+
|
|
538
|
+
// pi may run extensions under a non-node runtime (Bun), where process.execPath
|
|
539
|
+
// is not a node binary and spawning it with a script path silently fails.
|
|
540
|
+
// Same failure class the opencode live smoke caught on 2026-06-11.
|
|
541
|
+
const NODE_BIN = basename(process.execPath).startsWith("node") ? process.execPath : "node";
|
|
542
|
+
|
|
543
|
+
export default function (pi: ExtensionAPI) {
|
|
544
|
+
const root = process.cwd();
|
|
545
|
+
|
|
546
|
+
function runAdapter(adapterScript: string, eventName: string, hookId: string, relScript: string): { allow: boolean; context?: string; reason?: string } {
|
|
547
|
+
const adapterPath = join(root, "scripts", "hooks", adapterScript);
|
|
548
|
+
const payload = JSON.stringify({ hook_event_name: eventName, cwd: root });
|
|
549
|
+
const result = spawnSync(NODE_BIN, [adapterPath, eventName, hookId, relScript, "default"], {
|
|
550
|
+
input: payload,
|
|
551
|
+
encoding: "utf8",
|
|
552
|
+
cwd: root,
|
|
553
|
+
env: { ...process.env, FLOW_AGENTS_HOOK_RUNTIME: "pi" },
|
|
554
|
+
timeout: 30000,
|
|
555
|
+
});
|
|
556
|
+
try {
|
|
557
|
+
return JSON.parse(result.stdout || "{}") as { allow: boolean; context?: string; reason?: string };
|
|
558
|
+
} catch {
|
|
559
|
+
return { allow: true };
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function runTelemetry(eventName: string): void {
|
|
564
|
+
const telemetryPath = join(root, "scripts", "hooks", "pi-telemetry-hook.js");
|
|
565
|
+
const payload = JSON.stringify({ hook_event_name: eventName, cwd: root });
|
|
566
|
+
spawnSync(NODE_BIN, [telemetryPath, eventName, "dev"], {
|
|
567
|
+
input: payload,
|
|
568
|
+
encoding: "utf8",
|
|
569
|
+
cwd: root,
|
|
570
|
+
env: { ...process.env, FLOW_AGENTS_TELEMETRY_RUNTIME: "pi" },
|
|
571
|
+
timeout: 10000,
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
pi.on("session_start", async (_event, _ctx) => {
|
|
576
|
+
runTelemetry("session_start");
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
pi.on("before_agent_start", async (event, _ctx) => {
|
|
580
|
+
runTelemetry("before_agent_start");
|
|
581
|
+
// Inject workflow steering context at agent start
|
|
582
|
+
const result = runAdapter("pi-hook-adapter.js", "before_agent_start", "workflow-steering", "workflow-steering.js");
|
|
583
|
+
if (result.context) {
|
|
584
|
+
return {
|
|
585
|
+
systemPrompt: event.systemPrompt + "\\n\\n" + result.context,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
pi.on("tool_call", async (event, _ctx) => {
|
|
591
|
+
runTelemetry("tool_call");
|
|
592
|
+
const result = runAdapter("pi-hook-adapter.js", "tool_call", "config-protection", "config-protection.js");
|
|
593
|
+
if (result && result.allow === false) {
|
|
594
|
+
return { block: true, reason: result.reason || "Blocked by Flow Agents hook policy." };
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
pi.on("tool_result", async (_event, _ctx) => {
|
|
599
|
+
runTelemetry("tool_result");
|
|
600
|
+
runAdapter("pi-hook-adapter.js", "tool_result", "quality-gate", "quality-gate.js");
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
pi.on("session_shutdown", async (_event, _ctx) => {
|
|
604
|
+
runTelemetry("session_shutdown");
|
|
605
|
+
runAdapter("pi-hook-adapter.js", "session_shutdown", "stop-goal-fit", "stop-goal-fit.js");
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
`;
|
|
609
|
+
}
|
|
610
|
+
function buildPi(agents) {
|
|
611
|
+
const bundle = path.join(dist, "pi");
|
|
612
|
+
resetDir(bundle);
|
|
613
|
+
copySharedContent(bundle, "pi", "<bundle-root>");
|
|
614
|
+
writeText(path.join(bundle, manifest.pi.task_dir, ".gitkeep"), "");
|
|
615
|
+
// pi has no named-subagent registry; agents are left canonical/unexported.
|
|
616
|
+
// Skills are exported to .pi/skills/ (direct .md files supported in that dir).
|
|
617
|
+
for (const skill of fs.readdirSync(path.join(root, "skills"))) {
|
|
618
|
+
const skillPath = path.join(root, "skills", skill, "SKILL.md");
|
|
619
|
+
if (fs.existsSync(skillPath))
|
|
620
|
+
writeText(path.join(bundle, ".pi/skills", skill, "SKILL.md"), sanitizeText(readText(skillPath), "pi", "<bundle-root>"));
|
|
621
|
+
}
|
|
622
|
+
writeText(path.join(bundle, ".pi/extensions/flow-agents.ts"), exportPiExtension());
|
|
623
|
+
writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("pi", agents, manifest.pi.task_dir));
|
|
624
|
+
writeText(path.join(bundle, "README.md"), exportTargetReadme("pi", "bash install.sh /path/to/workspace"));
|
|
625
|
+
writeText(path.join(bundle, "install.sh"), installScript("pi", "/path/to/workspace"));
|
|
626
|
+
fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
|
|
627
|
+
}
|
|
364
628
|
function buildCatalog(agents) {
|
|
365
629
|
const kitsCatalog = path.join(root, "kits/catalog.json");
|
|
366
630
|
return {
|
|
@@ -379,6 +643,8 @@ export function main() {
|
|
|
379
643
|
buildKiro(agents);
|
|
380
644
|
buildClaudeCode(agents);
|
|
381
645
|
buildCodex(agents);
|
|
646
|
+
buildOpencode(agents);
|
|
647
|
+
buildPi(agents);
|
|
382
648
|
writeText(path.join(dist, "catalog.json"), `${JSON.stringify(buildCatalog(agents), null, 2)}\n`);
|
|
383
649
|
writeText(path.join(dist, "README.md"), "# Universal Bundles\n\nRun `npm run build:bundles` from the repo root to regenerate these bundles.\n");
|
|
384
650
|
console.log("Built bundles:");
|
|
@@ -386,6 +652,8 @@ export function main() {
|
|
|
386
652
|
console.log(" - dist/kiro");
|
|
387
653
|
console.log(" - dist/claude-code");
|
|
388
654
|
console.log(" - dist/codex");
|
|
655
|
+
console.log(" - dist/opencode");
|
|
656
|
+
console.log(" - dist/pi");
|
|
389
657
|
if (printDiagnostics && dropDiagnostics.length) {
|
|
390
658
|
console.error("Export sanitization diagnostics:");
|
|
391
659
|
for (const item of dropDiagnostics)
|
|
@@ -108,10 +108,13 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
108
108
|
removed += pruneNamedDirs(rootDir, "skills", allPackMembers(packs, "skills"), selected.skills, dryRun);
|
|
109
109
|
removed += pruneNamedDirs(rootDir, ".claude/skills", allPackMembers(packs, "skills"), selected.skills, dryRun);
|
|
110
110
|
removed += pruneNamedDirs(rootDir, ".codex/skills", allPackMembers(packs, "skills"), selected.skills, dryRun);
|
|
111
|
+
removed += pruneNamedDirs(rootDir, ".opencode/skills", allPackMembers(packs, "skills"), selected.skills, dryRun);
|
|
112
|
+
removed += pruneNamedDirs(rootDir, ".pi/skills", allPackMembers(packs, "skills"), selected.skills, dryRun);
|
|
111
113
|
removed += pruneNamedDirs(rootDir, "powers", allPackMembers(packs, "powers"), selected.powers, dryRun);
|
|
112
114
|
removed += pruneAgentFiles(rootDir, "agents", ".json", allPackMembers(packs, "agents"), selected.agents, dryRun);
|
|
113
115
|
removed += pruneAgentFiles(rootDir, ".claude/agents", ".md", allPackMembers(packs, "agents"), selected.agents, dryRun);
|
|
114
116
|
removed += pruneAgentFiles(rootDir, ".codex/agents", ".toml", allPackMembers(packs, "agents"), selected.agents, dryRun);
|
|
117
|
+
removed += pruneAgentFiles(rootDir, ".opencode/agents", ".md", allPackMembers(packs, "agents"), selected.agents, dryRun);
|
|
115
118
|
const summary = {
|
|
116
119
|
selected_packs: [...selectedNames].sort(),
|
|
117
120
|
removed_entries: removed,
|
|
@@ -62,12 +62,17 @@ const hookFilePolicies = new Map([
|
|
|
62
62
|
["scripts/hooks/run-hook.js", { category: "hook runner", requiredNeedles: ["isHookEnabled", "Path traversal rejected"] }],
|
|
63
63
|
["scripts/hooks/config-protection.js", { category: "policy hook", requiredNeedles: ["Config Protection Hook"] }],
|
|
64
64
|
["scripts/hooks/governance-audit.sh", { category: "policy hook", requiredNeedles: ["governance-audit.sh", "audit_emit"] }],
|
|
65
|
+
["scripts/hooks/opencode-hook-adapter.js", { category: "runtime adapter", requiredNeedles: ["opencode", "run-hook.js"] }],
|
|
66
|
+
["scripts/hooks/opencode-telemetry-hook.js", { category: "telemetry shim", requiredNeedles: ["opencode", "telemetry"] }],
|
|
67
|
+
["scripts/hooks/pi-hook-adapter.js", { category: "runtime adapter", requiredNeedles: ["pi", "run-hook.js"] }],
|
|
68
|
+
["scripts/hooks/pi-telemetry-hook.js", { category: "telemetry shim", requiredNeedles: ["pi", "telemetry"] }],
|
|
65
69
|
["scripts/hooks/post-edit-accumulator.js", { category: "policy hook", requiredNeedles: ["Post-Edit"] }],
|
|
66
70
|
["scripts/hooks/pre-commit-quality.js", { category: "repo guardrail hook", requiredNeedles: ["staged"] }],
|
|
67
71
|
["scripts/hooks/quality-gate.js", { category: "policy hook", requiredNeedles: ["Quality"] }],
|
|
68
72
|
["scripts/hooks/report-only-guard.js", { category: "policy hook", requiredNeedles: ["Report-Only Guard Hook"] }],
|
|
69
73
|
["scripts/hooks/stop-format-typecheck.js", { category: "policy hook", requiredNeedles: ["Stop Hook", "typecheck"] }],
|
|
70
74
|
["scripts/hooks/stop-goal-fit.js", { category: "policy hook", requiredNeedles: ["Stop Hook", "Goal Fit"] }],
|
|
75
|
+
["scripts/hooks/utterance-check.js", { category: "policy hook", requiredNeedles: ["Utterance Check Hook", "FLOW_AGENTS_UTTERANCE_CHECK_ENABLED"] }],
|
|
71
76
|
["scripts/hooks/workflow-steering.js", { category: "policy hook", requiredNeedles: ["Workflow Steering Hook"] }],
|
|
72
77
|
["scripts/hooks/desktop-notify.sh", { category: "local notification helper", requiredNeedles: ["desktop-notify.sh", "osascript"] }],
|
|
73
78
|
["scripts/hooks/lib/audit-transport.sh", { category: "shared hook library", requiredNeedles: ["audit_emit"] }],
|
|
@@ -94,7 +99,7 @@ const requiredUsageFeedbackFiles = [
|
|
|
94
99
|
const fixtureOwnershipSelfAuditRefs = new Set([
|
|
95
100
|
"evals/integration/test_fixture_retirement_audit.sh",
|
|
96
101
|
]);
|
|
97
|
-
const pythonInventoryExcludes = new Set([".git", ".flow-agents", "node_modules", ".venv", "dist", "__pycache__", ".pytest_cache", ".cache", "build"]);
|
|
102
|
+
const pythonInventoryExcludes = new Set([".git", ".flow-agents", "node_modules", ".venv", "dist", "__pycache__", ".pytest_cache", ".cache", "build", "integrations"]);
|
|
98
103
|
const pythonCommandScanRoots = ["README.md", "docs", "context", "skills", "prompts", "agents", "evals", "scripts", "packaging", "package.json"];
|
|
99
104
|
const allowedPythonCommandFiles = [
|
|
100
105
|
/^agents\/tool-explore-deps\.json$/,
|
|
@@ -6,7 +6,11 @@ TELEMETRY_CONFIG_FILE="${TELEMETRY_CONFIG_FILE:-${TELEMETRY_DIR}/telemetry.conf}
|
|
|
6
6
|
|
|
7
7
|
# Defaults
|
|
8
8
|
TELEMETRY_ENABLED="${TELEMETRY_ENABLED:-true}"
|
|
9
|
-
|
|
9
|
+
# TELEMETRY_DIR is <workspace>/scripts/telemetry, so the workspace root is
|
|
10
|
+
# two levels up. Three levels escaped into the workspace's PARENT directory
|
|
11
|
+
# (caught by live acceptance smoke 2026-06-11: events landed in /tmp/.telemetry
|
|
12
|
+
# instead of the installed workspace).
|
|
13
|
+
TELEMETRY_DATA_DIR="${TELEMETRY_DATA_DIR:-$(cd "${TELEMETRY_DIR}/../.." && pwd)/.telemetry}"
|
|
10
14
|
TELEMETRY_SESSION_DIR="${TELEMETRY_SESSION_DIR:-${TELEMETRY_DATA_DIR}/sessions}"
|
|
11
15
|
TELEMETRY_ENRICH_SYSTEM="${TELEMETRY_ENRICH_SYSTEM:-true}"
|
|
12
16
|
TELEMETRY_ENRICH_WORKSPACE="${TELEMETRY_ENRICH_WORKSPACE:-true}"
|
|
@@ -86,7 +86,7 @@ Flow Agents works like an agent workbench with seven cooperating layers:
|
|
|
86
86
|
| Powers | Tool bundles and activation guidance for integrations. | `powers/` |
|
|
87
87
|
| Agents | Specialist roles with scoped responsibilities. | `agents/`, `agent-cards/` |
|
|
88
88
|
| Workflows | State, gates, handoffs, and task memory. | Kontour Flow concepts, `.flow-agents/`, `npm run workflow:sidecar --` |
|
|
89
|
-
| Hooks | Just-in-time reminders or blockers from current workflow state. | `hooks/`, exported runtime configs |
|
|
89
|
+
| Hooks | Just-in-time reminders or blockers from current workflow state. | `scripts/hooks/`, exported runtime configs |
|
|
90
90
|
| Evidence | Tests, evals, telemetry, findings, and outcome records. | `evals/`, `.telemetry/`, sidecars |
|
|
91
91
|
|
|
92
92
|
Each layer should stay small enough to explain independently. When the system feels complicated, the fix is usually to move behavior to the right layer, not to add more global prompt text.
|
|
@@ -252,13 +252,12 @@ The intended pattern is that every important workflow rule gets a test at the lo
|
|
|
252
252
|
|
|
253
253
|
Packs keep the global surface understandable.
|
|
254
254
|
|
|
255
|
-
`packaging/packs.json` groups capabilities into sets
|
|
255
|
+
`packaging/packs.json` groups capabilities into sets. Currently defined:
|
|
256
256
|
|
|
257
257
|
- `core`
|
|
258
258
|
- `development`
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
- `experimental`
|
|
259
|
+
|
|
260
|
+
Future packs (knowledge, AWS, experimental) are deferred until another producer proof shows repeated friction.
|
|
262
261
|
|
|
263
262
|
All-pack installs remain the default today. `FLOW_AGENTS_PACKS` lets users opt into a smaller installed surface, and domain depth belongs in packs so a global setup can be narrowed without changing the source bundle.
|
|
264
263
|
|
package/docs/context-map.md
CHANGED
|
@@ -50,6 +50,7 @@ Machine-readable workflow state lives beside Markdown artifacts in `.flow-agents
|
|
|
50
50
|
| Schema | Title | ID |
|
|
51
51
|
| --- | --- | --- |
|
|
52
52
|
| backlog-provider-settings.schema.json | Flow Agents Backlog Provider Settings | https://flow-agents.dev/schemas/backlog-provider-settings.schema.json |
|
|
53
|
+
| flow-agents-settings.schema.json | Flow Agents Settings | https://flow-agents.dev/schemas/flow-agents-settings.schema.json |
|
|
53
54
|
| workflow-acceptance.schema.json | Flow Agents Workflow Acceptance | https://flow-agents.dev/schemas/workflow-acceptance.schema.json |
|
|
54
55
|
| workflow-critique.schema.json | Flow Agents Workflow Critique | https://flow-agents.dev/schemas/workflow-critique.schema.json |
|
|
55
56
|
| workflow-evidence.schema.json | Flow Agents Workflow Evidence | https://flow-agents.dev/schemas/workflow-evidence.schema.json |
|