@kontourai/flow-agents 1.0.1 → 1.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/workflows/ci.yml +110 -0
- package/.github/workflows/runtime-compat.yml +5 -2
- package/CHANGELOG.md +42 -0
- package/README.md +26 -5
- package/build/src/cli/console-learning-projection.js +19 -2
- package/build/src/cli/effective-backlog-settings.js +18 -2
- package/build/src/cli/fixture-retirement-audit.js +19 -2
- package/build/src/cli/init.js +19 -2
- package/build/src/cli/{flow-kit.js → kit.js} +122 -108
- package/build/src/cli/promote-workflow-artifact.js +19 -2
- package/build/src/cli/publish-change-helper.js +19 -2
- package/build/src/cli/pull-work-provider.js +19 -2
- package/build/src/cli/runtime-adapter.js +20 -2
- package/build/src/cli/usage-feedback.js +19 -2
- package/build/src/cli/utterance-check.js +19 -2
- package/build/src/cli/validate-hook-influence.js +19 -2
- package/build/src/cli/validate-source-tree.js +4 -4
- package/build/src/cli/veritas-governance.js +19 -2
- package/build/src/cli/workflow-artifact-cleanup-audit.js +19 -2
- package/build/src/cli.js +3 -3
- package/build/src/flow-kit/validate.js +58 -62
- package/build/src/runtime-adapters.js +55 -24
- package/build/src/tools/build-universal-bundles.js +83 -19
- package/build/src/tools/generate-context-map.js +68 -9
- package/build/src/tools/validate-package.js +19 -2
- package/build/src/tools/validate-source-tree.js +51 -3
- package/context/scripts/telemetry/console-presets.sh +1 -1
- package/docs/adr/0007-flow-skill-kit-tool-boundary.md +169 -0
- package/docs/adr/0007-skill-audit.md +112 -0
- package/docs/adr/0008-kit-operation-boundary.md +88 -0
- package/docs/context-map.md +18 -22
- package/docs/flow-kit-repository-contract.md +5 -5
- package/docs/getting-started.md +177 -0
- package/docs/index.md +19 -8
- package/docs/kit-authoring-guide.md +46 -10
- package/docs/knowledge-kit.md +2 -2
- package/docs/spec/runtime-hook-surface.md +1 -1
- package/docs/vision.md +1 -1
- package/docs/workflow-usage-guide.md +1 -1
- package/evals/ci/run-baseline.sh +55 -8
- package/evals/fixtures/builder-kit-workflow-state/happy-path.json +2 -2
- package/evals/fixtures/builder-kit-workflow-state/mid-work-resume.json +2 -2
- package/evals/fixtures/console-learning-projection/artifacts/console-learning-correction/learning.json +1 -1
- package/evals/fixtures/pull-work-provider/github-issues.json +5 -5
- package/evals/integration/test_activate_npx_context.sh +2 -2
- package/evals/integration/test_bundle_install.sh +17 -12
- package/evals/integration/test_console_learning_projection.sh +1 -1
- package/evals/integration/test_flow_kit_install_git.sh +7 -7
- package/evals/integration/test_flow_kit_repository.sh +4 -4
- package/evals/integration/test_kit_conformance_levels.sh +1 -1
- package/evals/integration/test_local_flow_kit_install.sh +7 -7
- package/evals/integration/test_publish_change_helper.sh +1 -1
- package/evals/integration/test_pull_work_provider.sh +1 -1
- package/evals/integration/test_runtime_adapter_activation.sh +140 -19
- package/evals/lib/node.sh +2 -2
- package/evals/run.sh +2 -0
- package/evals/static/test_console_presets.sh +49 -0
- package/evals/static/test_workflow_skills.sh +15 -15
- package/integrations/strands/flow_agents_strands/steering.py +1 -1
- package/integrations/strands-ts/src/hooks.ts +1 -1
- package/kits/builder/kit.json +17 -0
- package/{skills → kits/builder/skills}/builder-shape/SKILL.md +4 -4
- package/{skills → kits/builder/skills}/idea-to-backlog/SKILL.md +1 -1
- package/kits/knowledge/kit.json +16 -9
- package/package.json +8 -5
- package/packaging/packs.json +1 -21
- package/scripts/README.md +1 -1
- package/scripts/kit.js +2 -0
- package/scripts/telemetry/console-presets.sh +1 -1
- package/skills/README.md +23 -0
- package/src/cli/console-learning-projection.ts +7 -1
- package/src/cli/effective-backlog-settings.ts +6 -1
- package/src/cli/fixture-retirement-audit.ts +7 -1
- package/src/cli/init.ts +7 -1
- package/src/cli/{flow-kit.ts → kit.ts} +124 -109
- package/src/cli/promote-workflow-artifact.ts +7 -1
- package/src/cli/publish-change-helper.ts +7 -1
- package/src/cli/pull-work-provider.ts +7 -1
- package/src/cli/runtime-adapter.ts +8 -1
- package/src/cli/usage-feedback.ts +7 -1
- package/src/cli/utterance-check.ts +7 -1
- package/src/cli/validate-hook-influence.ts +7 -1
- package/src/cli/validate-source-tree.ts +4 -4
- package/src/cli/veritas-governance.ts +7 -1
- package/src/cli/workflow-artifact-cleanup-audit.ts +7 -1
- package/src/cli.ts +3 -3
- package/src/flow-kit/validate.ts +63 -57
- package/src/runtime-adapters.ts +54 -26
- package/src/tools/build-universal-bundles.ts +67 -14
- package/src/tools/generate-context-map.ts +43 -7
- package/src/tools/validate-package.ts +7 -1
- package/src/tools/validate-source-tree.ts +34 -2
- package/scripts/flow-kit.js +0 -2
- package/skills/context-budget/SKILL.md +0 -40
- package/skills/explore/SKILL.md +0 -137
- package/skills/feedback-loop/SKILL.md +0 -87
- package/skills/frontend-design/SKILL.md +0 -80
- /package/{skills → kits/builder/skills}/deliver/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/design-probe/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/evidence-gate/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/execute-plan/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/fix-bug/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/learning-review/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/pickup-probe/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/plan-work/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/pull-work/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/release-readiness/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/review-work/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/tdd-workflow/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/verify-work/SKILL.md +0 -0
- /package/{skills → kits/knowledge/skills}/knowledge-capture/SKILL.md +0 -0
package/src/cli.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { basename } from "node:path";
|
|
3
3
|
import { main as effectiveBacklogSettings } from "./cli/effective-backlog-settings.js";
|
|
4
4
|
import { main as consoleLearningProjection } from "./cli/console-learning-projection.js";
|
|
5
|
-
import { main as
|
|
5
|
+
import { main as kit } from "./cli/kit.js";
|
|
6
6
|
import { main as fixtureRetirementAudit } from "./cli/fixture-retirement-audit.js";
|
|
7
7
|
import { main as init } from "./cli/init.js";
|
|
8
8
|
import { main as promoteWorkflowArtifact } from "./cli/promote-workflow-artifact.js";
|
|
@@ -28,7 +28,7 @@ const availableCommands = new Map<string, (argv: string[]) => number | Promise<n
|
|
|
28
28
|
["effective-backlog-settings", effectiveBacklogSettings],
|
|
29
29
|
["filter-installed-packs", filterInstalledPacks],
|
|
30
30
|
["fixture-retirement-audit", fixtureRetirementAudit],
|
|
31
|
-
["
|
|
31
|
+
["kit", kit],
|
|
32
32
|
["init", init],
|
|
33
33
|
["promote-workflow-artifact", promoteWorkflowArtifact],
|
|
34
34
|
["publish-change", publishChange],
|
|
@@ -51,7 +51,7 @@ const aliases = new Map<string, string>([
|
|
|
51
51
|
["flow-agents-effective-backlog-settings", "effective-backlog-settings"],
|
|
52
52
|
["flow-agents-filter-installed-packs", "filter-installed-packs"],
|
|
53
53
|
["flow-agents-fixture-retirement-audit", "fixture-retirement-audit"],
|
|
54
|
-
["flow-agents-
|
|
54
|
+
["flow-agents-kit", "kit"],
|
|
55
55
|
["flow-agents-promote-workflow-artifact", "promote-workflow-artifact"],
|
|
56
56
|
["flow-agents-publish-change", "publish-change"],
|
|
57
57
|
["flow-agents-pull-work-provider", "pull-work-provider"],
|
package/src/flow-kit/validate.ts
CHANGED
|
@@ -2,7 +2,8 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { readJson } from "../lib/fs.js";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// Extension-only asset classes: validated by Flow Agents. Flows are validated by @kontourai/flow.
|
|
6
|
+
const EXTENSION_ASSET_CLASSES = ["skills", "docs", "adapters", "evals", "assets"] as const;
|
|
6
7
|
|
|
7
8
|
// Core container fields owned by kontourai/flow (flow-kit-container.schema.json).
|
|
8
9
|
// agent-extension fields are skills, docs, adapters, evals, assets.
|
|
@@ -33,43 +34,52 @@ export interface KitTargetsResult {
|
|
|
33
34
|
third_party_extensions: string[];
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
// Lazy-loaded cache for validateKitContainer from @kontourai/flow.
|
|
38
|
+
// list/status/activate are runtime ops that never call validation and must NOT load
|
|
39
|
+
// @kontourai/flow (it is unresolvable in a standalone installed bundle).
|
|
40
|
+
// Only validate/inspect (authoring ops) trigger this load.
|
|
41
|
+
type ValidateKitContainerFn = (kitDir: string, manifest: Record<string, unknown>) => { valid: boolean; diagnostics: { severity: string; path: string; message: string }[] };
|
|
42
|
+
let _validateKitContainerCache: ValidateKitContainerFn | null = null;
|
|
43
|
+
|
|
44
|
+
async function loadValidateKitContainer(): Promise<ValidateKitContainerFn> {
|
|
45
|
+
if (_validateKitContainerCache) return _validateKitContainerCache;
|
|
46
|
+
let mod: { validateKitContainer?: unknown };
|
|
47
|
+
try {
|
|
48
|
+
mod = await import("@kontourai/flow") as { validateKitContainer?: unknown };
|
|
49
|
+
} catch (err) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
"container validation requires @kontourai/flow; run from an npm-installed flow-agents workspace " +
|
|
52
|
+
`or use 'flow kit validate' (original error: ${(err as Error).message})`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (typeof mod.validateKitContainer !== "function") {
|
|
56
|
+
throw new Error("@kontourai/flow did not export validateKitContainer");
|
|
57
|
+
}
|
|
58
|
+
_validateKitContainerCache = mod.validateKitContainer as ValidateKitContainerFn;
|
|
59
|
+
return _validateKitContainerCache;
|
|
60
|
+
}
|
|
61
|
+
|
|
36
62
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* Returns a list of violation messages (empty = valid).
|
|
63
|
+
* Delegates core Flow Kit container validation to @kontourai/flow's validateKitContainer.
|
|
64
|
+
* The container contract lives once, in Flow. Returns a list of violation messages (empty = valid).
|
|
40
65
|
*
|
|
41
66
|
* The degradation invariant: every Flow Agents Kit MUST remain a valid core
|
|
42
67
|
* Flow Kit container when agent-extension fields are ignored.
|
|
68
|
+
*
|
|
69
|
+
* Loads @kontourai/flow lazily (on first call) so that runtime ops (list/status/activate)
|
|
70
|
+
* that never invoke validation can run in standalone installed bundles where
|
|
71
|
+
* @kontourai/flow is not present.
|
|
72
|
+
*
|
|
73
|
+
* @param kitDir Real kit directory path for file-existence checks on flows[].path entries.
|
|
74
|
+
* Pass the actual kit directory when available; pass "" for structural-only checks.
|
|
43
75
|
*/
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
if (typeof manifest.name !== "string" || !manifest.name.trim()) {
|
|
53
|
-
errors.push(`${label}: .name must be a non-empty string`);
|
|
54
|
-
}
|
|
55
|
-
if (!Array.isArray(manifest.flows) || manifest.flows.length === 0) {
|
|
56
|
-
errors.push(`${label}: .flows must be a non-empty list`);
|
|
57
|
-
} else {
|
|
58
|
-
manifest.flows.forEach((entry: unknown, index: number) => {
|
|
59
|
-
if (typeof entry !== "object" || entry === null) {
|
|
60
|
-
errors.push(`${label}: flows[${index}] must be an object`);
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
const flow = entry as Record<string, unknown>;
|
|
64
|
-
if (typeof flow.id !== "string" || !flow.id) {
|
|
65
|
-
errors.push(`${label}: flows[${index}].id must be a string`);
|
|
66
|
-
}
|
|
67
|
-
if (typeof flow.path !== "string" || !flow.path) {
|
|
68
|
-
errors.push(`${label}: flows[${index}].path must be a string`);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
return errors;
|
|
76
|
+
async function delegateCoreContainerValidation(kitDir: string, manifest: Record<string, unknown>): Promise<string[]> {
|
|
77
|
+
const validateKitContainer = await loadValidateKitContainer();
|
|
78
|
+
const result = validateKitContainer(kitDir, manifest);
|
|
79
|
+
if (result.valid) return [];
|
|
80
|
+
return result.diagnostics
|
|
81
|
+
.filter((d) => d.severity === "error")
|
|
82
|
+
.map((d) => `${d.path}: ${d.message}`);
|
|
73
83
|
}
|
|
74
84
|
|
|
75
85
|
/**
|
|
@@ -83,12 +93,17 @@ export function validateCoreContainer(manifest: Record<string, unknown>, label:
|
|
|
83
93
|
* - targets.flow: always present when K0 (any Flow consumer can evaluate gates).
|
|
84
94
|
* - targets.flow-agents: present when K1 (agent extension assets activate in >=1 harness).
|
|
85
95
|
* - third-party: any top-level keys that are not core fields and not Flow Agents extension classes.
|
|
96
|
+
*
|
|
97
|
+
* @param manifest The kit.json manifest object.
|
|
98
|
+
* @param kitDir Kit directory for flow file-existence checks. Defaults to "" (structural-only).
|
|
99
|
+
* Pass the real kit directory from `inspect` to get authoritative K0 validation.
|
|
86
100
|
*/
|
|
87
|
-
export function deriveKitTargets(manifest: Record<string, unknown
|
|
101
|
+
export async function deriveKitTargets(manifest: Record<string, unknown>, kitDir = ""): Promise<KitTargetsResult> {
|
|
88
102
|
const kitId = typeof manifest.id === "string" ? manifest.id : "<unknown>";
|
|
89
103
|
const kitName = typeof manifest.name === "string" ? manifest.name : "<unknown>";
|
|
90
104
|
|
|
91
|
-
|
|
105
|
+
// Delegate core container validation to @kontourai/flow.
|
|
106
|
+
const coreErrors = await delegateCoreContainerValidation(kitDir, manifest);
|
|
92
107
|
const k0 = coreErrors.length === 0;
|
|
93
108
|
|
|
94
109
|
const hasAgentExtension = AGENT_EXTENSION_CLASSES.size > 0 &&
|
|
@@ -119,7 +134,7 @@ export function deriveKitTargets(manifest: Record<string, unknown>): KitTargetsR
|
|
|
119
134
|
};
|
|
120
135
|
}
|
|
121
136
|
|
|
122
|
-
export function validateKitRepository(kitDir: string): string[] {
|
|
137
|
+
export async function validateKitRepository(kitDir: string): Promise<string[]> {
|
|
123
138
|
const errors: string[] = [];
|
|
124
139
|
const manifestPath = path.join(kitDir, "kit.json");
|
|
125
140
|
let manifest: Record<string, unknown>;
|
|
@@ -129,25 +144,17 @@ export function validateKitRepository(kitDir: string): string[] {
|
|
|
129
144
|
errors.push(`${manifestPath}: invalid JSON: ${(error as Error).message}`);
|
|
130
145
|
return errors;
|
|
131
146
|
}
|
|
132
|
-
if (manifest.schema_version !== "1.0") errors.push(`${manifestPath}: .schema_version must be "1.0"`);
|
|
133
|
-
if (typeof manifest.id !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(manifest.id)) {
|
|
134
|
-
errors.push(`${manifestPath}: .id must be a stable kebab-case string`);
|
|
135
|
-
}
|
|
136
|
-
if (typeof manifest.name !== "string" || !manifest.name.trim()) errors.push(`${manifestPath}: .name must be a non-empty string`);
|
|
137
147
|
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const coreErrors = validateCoreContainer(coreManifest, manifestPath);
|
|
145
|
-
for (const err of coreErrors) {
|
|
146
|
-
// Deduplicate: only add if not already covered by top-level checks above.
|
|
147
|
-
if (!errors.some((existing) => existing === err)) errors.push(err);
|
|
148
|
-
}
|
|
148
|
+
// Delegate core container validation (schema_version, id, name, flows including file
|
|
149
|
+
// existence) to @kontourai/flow — the container contract lives once, in Flow.
|
|
150
|
+
// This enforces the degradation invariant: a Flow Agents Kit must remain a valid
|
|
151
|
+
// core Flow Kit container when extension fields are stripped.
|
|
152
|
+
const coreErrors = await delegateCoreContainerValidation(kitDir, manifest);
|
|
153
|
+
for (const err of coreErrors) errors.push(err);
|
|
149
154
|
|
|
150
|
-
|
|
155
|
+
// Flow Agents extension validation: skills, docs, adapters, evals, assets.
|
|
156
|
+
// Flows are validated above by @kontourai/flow; only extension classes are checked here.
|
|
157
|
+
for (const section of EXTENSION_ASSET_CLASSES) {
|
|
151
158
|
const entries = manifest[section];
|
|
152
159
|
if (entries === undefined) continue;
|
|
153
160
|
if (!Array.isArray(entries)) {
|
|
@@ -182,16 +189,15 @@ export function validateKitRepository(kitDir: string): string[] {
|
|
|
182
189
|
return;
|
|
183
190
|
}
|
|
184
191
|
if (!fs.existsSync(resolved)) {
|
|
185
|
-
|
|
186
|
-
errors.push(`${manifestPath}: ${section}[${index}].path points at missing ${noun}: ${rel}`);
|
|
192
|
+
errors.push(`${manifestPath}: ${section}[${index}].path points at missing asset: ${rel}`);
|
|
187
193
|
}
|
|
188
194
|
});
|
|
189
195
|
}
|
|
190
196
|
return errors;
|
|
191
197
|
}
|
|
192
198
|
|
|
193
|
-
export function assertKitRepository(kitDir: string): Record<string, unknown
|
|
194
|
-
const errors = validateKitRepository(kitDir);
|
|
199
|
+
export async function assertKitRepository(kitDir: string): Promise<Record<string, unknown>> {
|
|
200
|
+
const errors = await validateKitRepository(kitDir);
|
|
195
201
|
if (errors.length) {
|
|
196
202
|
const error = new Error("Flow Kit repository validation failed") as Error & { diagnostics?: string[] };
|
|
197
203
|
error.diagnostics = errors;
|
package/src/runtime-adapters.ts
CHANGED
|
@@ -126,31 +126,47 @@ function safeSegment(value: string): string {
|
|
|
126
126
|
return value.replace(/[^A-Za-z0-9_.-]+/g, "-").replace(/^[.-]+|[.-]+$/g, "") || "asset";
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
// Asset classes that are directly activated (copied to the runtime directory) by both adapters.
|
|
130
|
+
// flows: gate definitions read by the adapter's flow-routing layer.
|
|
131
|
+
// skills: agent guidance markdown copied to skills/<kit-id>/ for agent discovery.
|
|
132
|
+
// docs: documentation markdown copied to docs/<kit-id>/ for agent reference.
|
|
133
|
+
const ACTIVATED_ASSET_CLASSES = new Set(["flows", "skills", "docs"]);
|
|
134
|
+
|
|
129
135
|
export function activateCodexLocal(sourceRoot: string, dest: string): Record<string, unknown> {
|
|
130
136
|
const inventory = readKitInventory(sourceRoot, dest);
|
|
131
137
|
const runtimeDir = path.join(dest, ".flow-agents", "runtime", "codex");
|
|
132
138
|
const generated: Record<string, string>[] = [];
|
|
133
139
|
const skipped: Record<string, string | null>[] = [];
|
|
134
140
|
for (const asset of inventory.assets) {
|
|
135
|
-
if (asset.asset_class
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
141
|
+
if (asset.asset_class === "flows") {
|
|
142
|
+
if (!asset.asset_id) {
|
|
143
|
+
skipped.push({ asset_class: asset.asset_class, path: asset.relative_path, kit_id: asset.kit_id, asset_id: null, reason: "flow asset is missing an id" });
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const output = path.join(runtimeDir, "flows", safeSegment(asset.kit_id), `${safeSegment(asset.asset_id)}.flow.json`);
|
|
147
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
148
|
+
fs.copyFileSync(asset.source_path, output);
|
|
149
|
+
generated.push({ asset_class: asset.asset_class, path: relPath(dest, output), kit_id: asset.kit_id, asset_id: asset.asset_id, source_path: asset.source_path.split(path.sep).join("/") });
|
|
150
|
+
} else if (asset.asset_class === "skills" || asset.asset_class === "docs") {
|
|
151
|
+
// Copy skills and docs to runtime/<adapter>/<class>/<kit-id>/<filename> so the
|
|
152
|
+
// agent's guidance index (AGENTS.md) can reference them and they are co-located
|
|
153
|
+
// with flow definitions for the same kit.
|
|
154
|
+
const filename = path.basename(asset.source_path);
|
|
155
|
+
const output = path.join(runtimeDir, asset.asset_class, safeSegment(asset.kit_id), filename);
|
|
156
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
157
|
+
fs.copyFileSync(asset.source_path, output);
|
|
158
|
+
generated.push({ asset_class: asset.asset_class, path: relPath(dest, output), kit_id: asset.kit_id, asset_id: asset.asset_id ?? "", source_path: asset.source_path.split(path.sep).join("/") });
|
|
159
|
+
} else {
|
|
160
|
+
skipped.push({ asset_class: asset.asset_class, path: asset.relative_path, kit_id: asset.kit_id, asset_id: asset.asset_id, reason: "asset class is not activated by codex-local" });
|
|
142
161
|
}
|
|
143
|
-
const output = path.join(runtimeDir, "flows", safeSegment(asset.kit_id), `${safeSegment(asset.asset_id)}.flow.json`);
|
|
144
|
-
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
145
|
-
fs.copyFileSync(asset.source_path, output);
|
|
146
|
-
generated.push({ asset_class: asset.asset_class, path: relPath(dest, output), kit_id: asset.kit_id, asset_id: asset.asset_id, source_path: asset.source_path.split(path.sep).join("/") });
|
|
147
162
|
}
|
|
148
163
|
fs.mkdirSync(runtimeDir, { recursive: true });
|
|
149
|
-
const
|
|
164
|
+
const supportedClasses = Array.from(ACTIVATED_ASSET_CLASSES);
|
|
165
|
+
const manifest = { schema_version: "1.0", adapter: "codex-local", supported_asset_classes: supportedClasses, generated_runtime_files: generated, skipped_assets: skipped, warnings: inventory.warnings, errors: inventory.errors };
|
|
150
166
|
const manifestPath = path.join(runtimeDir, "activation.json");
|
|
151
167
|
writeJson(manifestPath, manifest);
|
|
152
168
|
generated.push({ asset_class: "activation-manifest", path: relPath(dest, manifestPath), kit_id: "runtime", asset_id: "codex-local.activation", source_path: manifestPath.split(path.sep).join("/") });
|
|
153
|
-
return { selected_adapter: "codex-local", supported_asset_classes:
|
|
169
|
+
return { selected_adapter: "codex-local", supported_asset_classes: supportedClasses, generated_runtime_files: generated, skipped_assets: skipped, warnings: inventory.warnings, errors: inventory.errors };
|
|
154
170
|
}
|
|
155
171
|
|
|
156
172
|
// Decision Q3 (Issue #32): Option (a) — new adapter id "strands-local" rather than
|
|
@@ -163,27 +179,39 @@ export function activateStrandsLocal(sourceRoot: string, dest: string): Record<s
|
|
|
163
179
|
const inventory = readKitInventory(sourceRoot, dest);
|
|
164
180
|
// Runtime flows land at .flow-agents/runtime/strands/flows/<kit-id>/<asset-id>.flow.json
|
|
165
181
|
// so the Strands steering context can glob for *.flow.json under this path.
|
|
182
|
+
// Runtime skills land at .flow-agents/runtime/strands/skills/<kit-id>/<filename> and
|
|
183
|
+
// docs at .flow-agents/runtime/strands/docs/<kit-id>/<filename> for system-prompt injection.
|
|
166
184
|
const runtimeDir = path.join(dest, ".flow-agents", "runtime", "strands");
|
|
167
185
|
const generated: Record<string, string>[] = [];
|
|
168
186
|
const skipped: Record<string, string | null>[] = [];
|
|
169
187
|
for (const asset of inventory.assets) {
|
|
170
|
-
if (asset.asset_class
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
188
|
+
if (asset.asset_class === "flows") {
|
|
189
|
+
if (!asset.asset_id) {
|
|
190
|
+
skipped.push({ asset_class: asset.asset_class, path: asset.relative_path, kit_id: asset.kit_id, asset_id: null, reason: "flow asset is missing an id" });
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const output = path.join(runtimeDir, "flows", safeSegment(asset.kit_id), `${safeSegment(asset.asset_id)}.flow.json`);
|
|
194
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
195
|
+
fs.copyFileSync(asset.source_path, output);
|
|
196
|
+
generated.push({ asset_class: asset.asset_class, path: relPath(dest, output), kit_id: asset.kit_id, asset_id: asset.asset_id, source_path: asset.source_path.split(path.sep).join("/") });
|
|
197
|
+
} else if (asset.asset_class === "skills" || asset.asset_class === "docs") {
|
|
198
|
+
// Mirror the codex-local layout: strands/<class>/<kit-id>/<filename>.
|
|
199
|
+
// The Strands system-prompt injection layer can glob for all *.md files under
|
|
200
|
+
// .flow-agents/runtime/strands/skills/ to include agent guidance in the context.
|
|
201
|
+
const filename = path.basename(asset.source_path);
|
|
202
|
+
const output = path.join(runtimeDir, asset.asset_class, safeSegment(asset.kit_id), filename);
|
|
203
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
204
|
+
fs.copyFileSync(asset.source_path, output);
|
|
205
|
+
generated.push({ asset_class: asset.asset_class, path: relPath(dest, output), kit_id: asset.kit_id, asset_id: asset.asset_id ?? "", source_path: asset.source_path.split(path.sep).join("/") });
|
|
206
|
+
} else {
|
|
207
|
+
skipped.push({ asset_class: asset.asset_class, path: asset.relative_path, kit_id: asset.kit_id, asset_id: asset.asset_id, reason: "asset class is not activated by strands-local" });
|
|
177
208
|
}
|
|
178
|
-
const output = path.join(runtimeDir, "flows", safeSegment(asset.kit_id), `${safeSegment(asset.asset_id)}.flow.json`);
|
|
179
|
-
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
180
|
-
fs.copyFileSync(asset.source_path, output);
|
|
181
|
-
generated.push({ asset_class: asset.asset_class, path: relPath(dest, output), kit_id: asset.kit_id, asset_id: asset.asset_id, source_path: asset.source_path.split(path.sep).join("/") });
|
|
182
209
|
}
|
|
183
210
|
fs.mkdirSync(runtimeDir, { recursive: true });
|
|
184
|
-
const
|
|
211
|
+
const supportedClasses = Array.from(ACTIVATED_ASSET_CLASSES);
|
|
212
|
+
const manifest = { schema_version: "1.0", adapter: "strands-local", supported_asset_classes: supportedClasses, generated_runtime_files: generated, skipped_assets: skipped, warnings: inventory.warnings, errors: inventory.errors };
|
|
185
213
|
const manifestPath = path.join(runtimeDir, "activation.json");
|
|
186
214
|
writeJson(manifestPath, manifest);
|
|
187
215
|
generated.push({ asset_class: "activation-manifest", path: relPath(dest, manifestPath), kit_id: "runtime", asset_id: "strands-local.activation", source_path: manifestPath.split(path.sep).join("/") });
|
|
188
|
-
return { selected_adapter: "strands-local", supported_asset_classes:
|
|
216
|
+
return { selected_adapter: "strands-local", supported_asset_classes: supportedClasses, generated_runtime_files: generated, skipped_assets: skipped, warnings: inventory.warnings, errors: inventory.errors };
|
|
189
217
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { loadJson, readText, root, walkFiles, writeText } from "./common.js";
|
|
5
6
|
|
|
@@ -11,6 +12,57 @@ const textExtensions = new Set([".css", ".html", ".js", ".json", ".md", ".sh", "
|
|
|
11
12
|
const dropDiagnostics: string[] = [];
|
|
12
13
|
const printDiagnostics = !["0", "false", "no"].includes(String(process.env.FLOW_AGENTS_EXPORT_DIAGNOSTICS ?? "1").toLowerCase());
|
|
13
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Collect all skill source paths across skills/ and kit-owned skills.
|
|
17
|
+
* Returns an array of {name, src} pairs where name is the install name
|
|
18
|
+
* (same as the directory name) and src is the absolute SKILL.md path.
|
|
19
|
+
* Kit-owned skills are discovered by reading kit.json `skills` arrays;
|
|
20
|
+
* each entry's `path` is resolved relative to the kit directory.
|
|
21
|
+
*/
|
|
22
|
+
function collectAllSkills(): Array<{ name: string; src: string }> {
|
|
23
|
+
const results: Array<{ name: string; src: string }> = [];
|
|
24
|
+
const seen = new Set<string>();
|
|
25
|
+
|
|
26
|
+
// 1. Top-level skills/ directory (tools pending reclassification).
|
|
27
|
+
const skillsDir = path.join(root, "skills");
|
|
28
|
+
if (fs.existsSync(skillsDir)) {
|
|
29
|
+
for (const skill of fs.readdirSync(skillsDir).sort()) {
|
|
30
|
+
const skillPath = path.join(skillsDir, skill, "SKILL.md");
|
|
31
|
+
if (fs.existsSync(skillPath) && !seen.has(skill)) {
|
|
32
|
+
seen.add(skill);
|
|
33
|
+
results.push({ name: skill, src: skillPath });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 2. Kit-owned skills declared in kits/<kit>/kit.json `skills` arrays.
|
|
39
|
+
const kitsDir = path.join(root, "kits");
|
|
40
|
+
if (fs.existsSync(kitsDir)) {
|
|
41
|
+
for (const kitName of fs.readdirSync(kitsDir).sort()) {
|
|
42
|
+
const kitJson = path.join(kitsDir, kitName, "kit.json");
|
|
43
|
+
if (!fs.existsSync(kitJson)) continue;
|
|
44
|
+
let kitManifest: Record<string, unknown>;
|
|
45
|
+
try { kitManifest = loadJson<Record<string, unknown>>(kitJson); } catch { continue; }
|
|
46
|
+
const skills = Array.isArray(kitManifest["skills"]) ? kitManifest["skills"] as unknown[] : [];
|
|
47
|
+
for (const entry of skills) {
|
|
48
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
49
|
+
const skillEntry = entry as Record<string, unknown>;
|
|
50
|
+
const relPath = typeof skillEntry["path"] === "string" ? skillEntry["path"] : null;
|
|
51
|
+
if (!relPath) continue;
|
|
52
|
+
// Derive install name from the directory containing SKILL.md (one level up).
|
|
53
|
+
const absPath = path.resolve(path.join(kitsDir, kitName), relPath);
|
|
54
|
+
const skillName = path.basename(path.dirname(absPath));
|
|
55
|
+
if (fs.existsSync(absPath) && !seen.has(skillName)) {
|
|
56
|
+
seen.add(skillName);
|
|
57
|
+
results.push({ name: skillName, src: absPath });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return results.sort((a, b) => a.name.localeCompare(b.name));
|
|
64
|
+
}
|
|
65
|
+
|
|
14
66
|
function resetDir(dir: string): void {
|
|
15
67
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
16
68
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -301,9 +353,8 @@ function buildClaudeCode(agents: Agent[]): void {
|
|
|
301
353
|
copySharedContent(bundle, "claude-code", "<bundle-root>");
|
|
302
354
|
writeText(path.join(bundle, manifest.claude_code.task_dir, ".gitkeep"), "");
|
|
303
355
|
for (const spec of agents) writeText(path.join(bundle, ".claude/agents", `${spec.name}.md`), exportClaudeAgent(spec));
|
|
304
|
-
for (const
|
|
305
|
-
|
|
306
|
-
if (fs.existsSync(skillPath)) writeText(path.join(bundle, ".claude/skills", skill, "SKILL.md"), sanitizeText(readText(skillPath), "claude-code", "<bundle-root>"));
|
|
356
|
+
for (const { name, src } of collectAllSkills()) {
|
|
357
|
+
writeText(path.join(bundle, ".claude/skills", name, "SKILL.md"), sanitizeText(readText(src), "claude-code", "<bundle-root>"));
|
|
307
358
|
}
|
|
308
359
|
writeText(path.join(bundle, ".claude/settings.json"), exportClaudeSettings());
|
|
309
360
|
writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Claude Code", agents, manifest.claude_code.task_dir));
|
|
@@ -323,9 +374,8 @@ function buildCodex(agents: Agent[]): void {
|
|
|
323
374
|
for (const [profileName, profile] of Object.entries(manifest.codex.profiles ?? {})) writeText(path.join(bundle, ".codex", `${profileName}.config.toml`), exportCodexProfileConfig(profile as Record<string, unknown>, settings));
|
|
324
375
|
writeText(path.join(bundle, ".codex/hooks.json"), exportCodexHooks());
|
|
325
376
|
for (const spec of targetAgents) writeText(path.join(bundle, ".codex/agents", `${spec.name}.toml`), exportCodexAgent(spec));
|
|
326
|
-
for (const
|
|
327
|
-
|
|
328
|
-
if (fs.existsSync(skillPath)) writeText(path.join(bundle, ".codex/skills", skill, "SKILL.md"), sanitizeText(readText(skillPath), "codex", "<bundle-root>"));
|
|
377
|
+
for (const { name, src } of collectAllSkills()) {
|
|
378
|
+
writeText(path.join(bundle, ".codex/skills", name, "SKILL.md"), sanitizeText(readText(src), "codex", "<bundle-root>"));
|
|
329
379
|
}
|
|
330
380
|
writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Codex", targetAgents, manifest.codex.task_dir));
|
|
331
381
|
writeText(path.join(bundle, "README.md"), exportTargetReadme("Codex", "bash install.sh /path/to/workspace"));
|
|
@@ -489,9 +539,8 @@ function buildOpencode(agents: Agent[]): void {
|
|
|
489
539
|
for (const spec of agents) {
|
|
490
540
|
writeText(path.join(bundle, ".opencode/agents", `${spec.name}.md`), exportOpencodeAgent(spec));
|
|
491
541
|
}
|
|
492
|
-
for (const
|
|
493
|
-
|
|
494
|
-
if (fs.existsSync(skillPath)) writeText(path.join(bundle, ".opencode/skills", skill, "SKILL.md"), sanitizeText(readText(skillPath), "opencode", "<bundle-root>"));
|
|
542
|
+
for (const { name, src } of collectAllSkills()) {
|
|
543
|
+
writeText(path.join(bundle, ".opencode/skills", name, "SKILL.md"), sanitizeText(readText(src), "opencode", "<bundle-root>"));
|
|
495
544
|
}
|
|
496
545
|
writeText(path.join(bundle, ".opencode/plugins/flow-agents.js"), exportOpencodePlugin());
|
|
497
546
|
writeText(path.join(bundle, "opencode.json"), exportOpencodeConfig());
|
|
@@ -601,9 +650,8 @@ function buildPi(agents: Agent[]): void {
|
|
|
601
650
|
writeText(path.join(bundle, manifest.pi.task_dir, ".gitkeep"), "");
|
|
602
651
|
// pi has no named-subagent registry; agents are left canonical/unexported.
|
|
603
652
|
// Skills are exported to .pi/skills/ (direct .md files supported in that dir).
|
|
604
|
-
for (const
|
|
605
|
-
|
|
606
|
-
if (fs.existsSync(skillPath)) writeText(path.join(bundle, ".pi/skills", skill, "SKILL.md"), sanitizeText(readText(skillPath), "pi", "<bundle-root>"));
|
|
653
|
+
for (const { name, src } of collectAllSkills()) {
|
|
654
|
+
writeText(path.join(bundle, ".pi/skills", name, "SKILL.md"), sanitizeText(readText(src), "pi", "<bundle-root>"));
|
|
607
655
|
}
|
|
608
656
|
writeText(path.join(bundle, ".pi/extensions/flow-agents.ts"), exportPiExtension());
|
|
609
657
|
writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("pi", agents, manifest.pi.task_dir));
|
|
@@ -616,7 +664,7 @@ function buildCatalog(agents: Agent[]): Record<string, unknown> {
|
|
|
616
664
|
return {
|
|
617
665
|
source_root: ".",
|
|
618
666
|
agents: agents.slice().sort((a, b) => a.name.localeCompare(b.name)).map((spec) => spec.name),
|
|
619
|
-
skills:
|
|
667
|
+
skills: collectAllSkills().map(({ name }) => name),
|
|
620
668
|
powers: fs.readdirSync(path.join(root, "powers")).filter((name) => fs.existsSync(path.join(root, "powers", name, "mcp.json"))).sort(),
|
|
621
669
|
packs: packs.packs ?? [],
|
|
622
670
|
kits: fs.existsSync(kitsCatalog) ? loadJson<Record<string, unknown>>(kitsCatalog).kits ?? [] : [],
|
|
@@ -646,4 +694,9 @@ export function main(): number {
|
|
|
646
694
|
}
|
|
647
695
|
return 0;
|
|
648
696
|
}
|
|
649
|
-
|
|
697
|
+
// Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
|
|
698
|
+
// Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
|
|
699
|
+
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
700
|
+
const _selfRealPath = (() => { try { return fs.realpathSync(fileURLToPath(import.meta.url)); } catch { return fileURLToPath(import.meta.url); } })();
|
|
701
|
+
const _argv1RealPath = (() => { try { return fs.realpathSync(process.argv[1]); } catch { return process.argv[1]; } })();
|
|
702
|
+
if (_selfRealPath === _argv1RealPath) { process.exitCode = main(); }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { exists, loadJson, markdownTable, oneLine, readText, rel, root, writeText } from "./common.js";
|
|
5
6
|
|
|
@@ -73,15 +74,45 @@ function repoShape(manifest: Record<string, unknown>): string[][] {
|
|
|
73
74
|
return rows;
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
/** Collect all skill {name, absPath} pairs from skills/ and kit-owned skills. */
|
|
78
|
+
function allSkillPaths(): Array<{ name: string; absPath: string }> {
|
|
79
|
+
const results: Array<{ name: string; absPath: string }> = [];
|
|
80
|
+
const seen = new Set<string>();
|
|
81
|
+
const skillsDir = path.join(root, "skills");
|
|
82
|
+
if (exists(skillsDir)) {
|
|
83
|
+
for (const name of fs.readdirSync(skillsDir).sort()) {
|
|
84
|
+
const absPath = path.join(skillsDir, name, "SKILL.md");
|
|
85
|
+
if (exists(absPath) && !seen.has(name)) { seen.add(name); results.push({ name, absPath }); }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const kitsDir = path.join(root, "kits");
|
|
89
|
+
if (exists(kitsDir)) {
|
|
90
|
+
for (const kitName of fs.readdirSync(kitsDir).sort()) {
|
|
91
|
+
const kitJson = path.join(kitsDir, kitName, "kit.json");
|
|
92
|
+
if (!exists(kitJson)) continue;
|
|
93
|
+
let kitManifest: Record<string, unknown>;
|
|
94
|
+
try { kitManifest = loadJson<Record<string, unknown>>(kitJson); } catch { continue; }
|
|
95
|
+
const skills = Array.isArray(kitManifest["skills"]) ? kitManifest["skills"] as unknown[] : [];
|
|
96
|
+
for (const entry of skills) {
|
|
97
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
98
|
+
const skillEntry = entry as Record<string, unknown>;
|
|
99
|
+
const relPath = typeof skillEntry["path"] === "string" ? skillEntry["path"] : null;
|
|
100
|
+
if (!relPath) continue;
|
|
101
|
+
const absPath = path.resolve(path.join(kitsDir, kitName), relPath);
|
|
102
|
+
const skillName = path.basename(path.dirname(absPath));
|
|
103
|
+
if (exists(absPath) && !seen.has(skillName)) { seen.add(skillName); results.push({ name: skillName, absPath }); }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return results.sort((a, b) => a.name.localeCompare(b.name));
|
|
108
|
+
}
|
|
109
|
+
|
|
76
110
|
function listSkillRows(): [string[][], string[][]] {
|
|
77
111
|
const workflowRows: string[][] = [];
|
|
78
112
|
const supportRows: string[][] = [];
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
if (!exists(skillPath)) continue;
|
|
83
|
-
const meta = frontmatter(readText(skillPath));
|
|
84
|
-
const row = [meta.name ?? name, rel(skillPath), oneLine(meta.description ?? "")];
|
|
113
|
+
for (const { name, absPath } of allSkillPaths()) {
|
|
114
|
+
const meta = frontmatter(readText(absPath));
|
|
115
|
+
const row = [meta.name ?? name, rel(absPath), oneLine(meta.description ?? "")];
|
|
85
116
|
if (workflowSkills.has(row[0])) workflowRows.push(row);
|
|
86
117
|
else supportRows.push(row);
|
|
87
118
|
}
|
|
@@ -196,4 +227,9 @@ export function main(argv = process.argv.slice(2)): number {
|
|
|
196
227
|
return 0;
|
|
197
228
|
}
|
|
198
229
|
|
|
199
|
-
|
|
230
|
+
// Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
|
|
231
|
+
// Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
|
|
232
|
+
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
233
|
+
const _selfRealPath = (() => { try { return fs.realpathSync(fileURLToPath(import.meta.url)); } catch { return fileURLToPath(import.meta.url); } })();
|
|
234
|
+
const _argv1RealPath = (() => { try { return fs.realpathSync(process.argv[1]); } catch { return process.argv[1]; } })();
|
|
235
|
+
if (_selfRealPath === _argv1RealPath) { process.exitCode = main(); }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
|
|
5
6
|
export function main(argv = process.argv.slice(2)): number {
|
|
@@ -54,4 +55,9 @@ export function main(argv = process.argv.slice(2)): number {
|
|
|
54
55
|
console.log(errors === 0 ? "Result: PASS" : `Result: FAIL (${errors} error(s))`);
|
|
55
56
|
return errors === 0 ? 0 : 1;
|
|
56
57
|
}
|
|
57
|
-
|
|
58
|
+
// Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
|
|
59
|
+
// Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
|
|
60
|
+
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
61
|
+
const _selfRealPath = (() => { try { return fs.realpathSync(fileURLToPath(import.meta.url)); } catch { return fileURLToPath(import.meta.url); } })();
|
|
62
|
+
const _argv1RealPath = (() => { try { return fs.realpathSync(process.argv[1]); } catch { return process.argv[1]; } })();
|
|
63
|
+
if (_selfRealPath === _argv1RealPath) { process.exitCode = main(); }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { spawnSync } from "node:child_process";
|
|
5
6
|
import { loadJson, readText, rel, root, walkFiles } from "./common.js";
|
|
@@ -36,7 +37,7 @@ const publicScriptWrappers = new Map<string, { target: string; significantLines:
|
|
|
36
37
|
] }],
|
|
37
38
|
["scripts/filter-installed-packs.js", { target: "../build/src/tools/filter-installed-packs.js", significantLines: ['import("../build/src/tools/filter-installed-packs.js").then(({ main }) => process.exit(main(process.argv.slice(2))));'] }],
|
|
38
39
|
["scripts/generate-context-map.js", { target: "../build/src/tools/generate-context-map.js", significantLines: ['import("../build/src/tools/generate-context-map.js").then(({ main }) => process.exit(main(process.argv.slice(2))));'] }],
|
|
39
|
-
["scripts/
|
|
40
|
+
["scripts/kit.js", { target: "../build/src/cli/kit.js", significantLines: ['import("../build/src/cli/kit.js").then(({ main }) => main().then((code) => process.exit(code)));'] }],
|
|
40
41
|
["scripts/pull-work-provider.js", { target: "../build/src/cli/pull-work-provider.js", significantLines: ['import("../build/src/cli/pull-work-provider.js").then(({ main }) => process.exit(main()));'] }],
|
|
41
42
|
["scripts/effective-backlog-settings.js", { target: "../build/src/cli/effective-backlog-settings.js", significantLines: ['import("../build/src/cli/effective-backlog-settings.js").then(({ main }) => process.exit(main()));'] }],
|
|
42
43
|
["scripts/publish-change-helper.js", { target: "../build/src/cli/publish-change-helper.js", significantLines: ['import("../build/src/cli/publish-change-helper.js").then(({ main }) => process.exit(main()));'] }],
|
|
@@ -300,6 +301,28 @@ function validateAgentPaths(reporter: Reporter, manifest: any): void {
|
|
|
300
301
|
}
|
|
301
302
|
}
|
|
302
303
|
function validateLegacyRefs(reporter: Reporter): void {
|
|
304
|
+
// Collect all kit-owned asset relative paths so legacy-ref scanning can skip matches
|
|
305
|
+
// that are subpaths of kit-owned assets. E.g. legacyRefRe matches "skills/plan-work/SKILL.md"
|
|
306
|
+
// within "kits/builder/skills/plan-work/SKILL.md"; the kit declares and validates these.
|
|
307
|
+
const kitOwnedSubPaths = new Set<string>();
|
|
308
|
+
const kitsDir = path.join(root, "kits");
|
|
309
|
+
if (fs.existsSync(kitsDir)) {
|
|
310
|
+
for (const kitName of fs.readdirSync(kitsDir)) {
|
|
311
|
+
const kitJson = path.join(kitsDir, kitName, "kit.json");
|
|
312
|
+
if (!fs.existsSync(kitJson)) continue;
|
|
313
|
+
try {
|
|
314
|
+
const kitManifest = loadJson<Record<string, unknown>>(kitJson);
|
|
315
|
+
for (const section of ["skills", "docs", "adapters", "evals", "assets"]) {
|
|
316
|
+
const entries = Array.isArray(kitManifest[section]) ? kitManifest[section] as unknown[] : [];
|
|
317
|
+
for (const entry of entries) {
|
|
318
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
319
|
+
const relPath = (entry as Record<string, unknown>)["path"];
|
|
320
|
+
if (typeof relPath === "string" && relPath) kitOwnedSubPaths.add(relPath);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
} catch { /* skip invalid kit.json */ }
|
|
324
|
+
}
|
|
325
|
+
}
|
|
303
326
|
for (const file of walkFiles(path.join(root, "evals")).sort()) {
|
|
304
327
|
if (!textRefExtensions.has(path.extname(file))) continue;
|
|
305
328
|
const parts = path.relative(path.join(root, "evals"), file).split(path.sep);
|
|
@@ -309,6 +332,10 @@ function validateLegacyRefs(reporter: Reporter): void {
|
|
|
309
332
|
const ref = match[0].replace(/[.,)'"\]]+$/, "");
|
|
310
333
|
if (/[{}$]/.test(ref)) continue;
|
|
311
334
|
if (ref.split(/[\\/]/).includes("node_modules")) continue;
|
|
335
|
+
// Skip refs that are declared kit-owned asset paths or their parent directories
|
|
336
|
+
// (e.g. "skills/plan-work/SKILL.md" or "skills/plan-work" matched inside
|
|
337
|
+
// "kits/builder/skills/plan-work/SKILL.md" in eval files).
|
|
338
|
+
if (kitOwnedSubPaths.has(ref) || [...kitOwnedSubPaths].some((p) => p.startsWith(ref + "/"))) continue;
|
|
312
339
|
const candidates = [path.join(root, ref), ...(ref.startsWith("evals/") ? [] : [path.join(root, "evals", ref)])];
|
|
313
340
|
if (!candidates.some(fs.existsSync)) reporter.fail(`${rel(file)}: references missing source path: ${ref}`);
|
|
314
341
|
}
|
|
@@ -491,4 +518,9 @@ export function main(argv = process.argv.slice(2)): number {
|
|
|
491
518
|
console.log("Source tree validation passed.");
|
|
492
519
|
return 0;
|
|
493
520
|
}
|
|
494
|
-
|
|
521
|
+
// Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
|
|
522
|
+
// Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
|
|
523
|
+
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
524
|
+
const _selfRealPath = (() => { try { return fs.realpathSync(fileURLToPath(import.meta.url)); } catch { return fileURLToPath(import.meta.url); } })();
|
|
525
|
+
const _argv1RealPath = (() => { try { return fs.realpathSync(process.argv[1]); } catch { return process.argv[1]; } })();
|
|
526
|
+
if (_selfRealPath === _argv1RealPath) { process.exitCode = main(); }
|
package/scripts/flow-kit.js
DELETED