@kontourai/flow-agents 1.3.0 → 2.0.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/CODEOWNERS +29 -0
- package/.github/actions/trust-verify/action.yml +145 -0
- package/.github/workflows/ci.yml +11 -4
- package/.github/workflows/kit-gates-demo.yml +2 -2
- package/.github/workflows/publish-npm.yml +10 -2
- package/.github/workflows/release-please.yml +1 -1
- package/.github/workflows/trust-reconcile.yml +113 -0
- package/AGENTS.md +13 -0
- package/CHANGELOG.md +103 -0
- package/CONTRIBUTING.md +4 -4
- package/README.md +1 -0
- package/agents/tool-planner.json +1 -1
- package/build/src/cli/console-learning-projection.d.ts +1 -0
- package/build/src/cli/effective-backlog-settings.d.ts +1 -0
- package/build/src/cli/fixture-retirement-audit.d.ts +2 -0
- package/build/src/cli/init.d.ts +17 -0
- package/build/src/cli/init.js +242 -20
- package/build/src/cli/kit.d.ts +1 -0
- package/build/src/cli/promote-workflow-artifact.d.ts +1 -0
- package/build/src/cli/publish-change-helper.d.ts +1 -0
- package/build/src/cli/pull-work-provider.d.ts +1 -0
- package/build/src/cli/runtime-adapter.d.ts +1 -0
- package/build/src/cli/telemetry-doctor.d.ts +1 -0
- package/build/src/cli/usage-feedback.d.ts +1 -0
- package/build/src/cli/utterance-check.d.ts +1 -0
- package/build/src/cli/validate-hook-influence.d.ts +1 -0
- package/build/src/cli/validate-source-tree.d.ts +1 -0
- package/build/src/cli/validate-workflow-artifacts.d.ts +2 -0
- package/build/src/cli/validate-workflow-artifacts.js +19 -2
- package/build/src/cli/verify.d.ts +1 -0
- package/build/src/cli/verify.js +90 -0
- package/build/src/cli/veritas-governance.d.ts +1 -0
- package/build/src/cli/workflow-artifact-cleanup-audit.d.ts +1 -0
- package/build/src/cli/workflow-sidecar.d.ts +324 -0
- package/build/src/cli/workflow-sidecar.js +1973 -90
- package/build/src/cli.d.ts +2 -0
- package/build/src/cli.js +2 -3
- package/build/src/flow-kit/validate.d.ts +81 -0
- package/build/src/index.d.ts +5 -0
- package/build/src/index.js +36 -0
- package/build/src/lib/args.d.ts +8 -0
- package/build/src/lib/flow-resolver.d.ts +82 -0
- package/build/src/lib/flow-resolver.js +237 -0
- package/build/src/lib/fs.d.ts +7 -0
- package/build/src/lib/workflow-learning-projection.d.ts +132 -0
- package/build/src/runtime-adapters.d.ts +18 -0
- package/build/src/tools/build-universal-bundles.d.ts +2 -0
- package/build/src/tools/build-universal-bundles.js +34 -22
- package/build/src/tools/common.d.ts +9 -0
- package/build/src/tools/generate-context-map.d.ts +2 -0
- package/build/src/tools/generate-context-map.js +3 -16
- package/build/src/tools/validate-package.d.ts +2 -0
- package/build/src/tools/validate-source-tree.d.ts +2 -0
- package/build/src/tools/validate-source-tree.js +42 -162
- package/context/contracts/artifact-contract.md +10 -0
- package/context/contracts/delivery-contract.md +1 -0
- package/context/contracts/review-contract.md +1 -0
- package/context/contracts/verification-contract.md +2 -0
- package/context/gate-awareness.md +39 -0
- package/context/scripts/hooks/stop-goal-fit.js +632 -70
- package/docs/adr/0001-flow-agents-consumes-flow.md +1 -1
- package/docs/adr/0002-flow-kits-as-extension-unit.md +1 -1
- package/docs/adr/0004-gates-expect-surface-claims.md +2 -0
- package/docs/adr/0005-kubernetes-inspired-resource-contracts.md +2 -0
- package/docs/adr/0007-skill-audit.md +1 -1
- package/docs/adr/0009-canonical-hook-core-kit-boundary.md +95 -0
- package/docs/adr/0010-workflow-trust-state-as-hachure-bundle.md +139 -0
- package/docs/adr/0011-mcp-posture.md +100 -0
- package/docs/adr/0012-agent-coordination-as-liveness-claims.md +119 -0
- package/docs/adr/0013-context-lifecycle.md +151 -0
- package/docs/adr/0014-core-vs-domain-kit-boundary.md +143 -0
- package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +120 -0
- package/docs/adr/0016-three-hard-boundary-model.md +71 -0
- package/docs/adr/0017-anti-gaming-trust-security-model.md +155 -0
- package/docs/agent-system-guidebook.md +5 -12
- package/docs/context-map.md +4 -10
- package/docs/developer-architecture.md +14 -0
- package/docs/index.md +3 -2
- package/docs/integrations/framework-adapter.md +19 -6
- package/docs/integrations/index.md +2 -2
- package/docs/north-star.md +4 -4
- package/docs/operating-layers.md +3 -3
- package/docs/plans/adr-0010-phase2-gate-recompute.md +55 -0
- package/docs/repository-structure.md +2 -2
- package/docs/skills-map.md +1 -0
- package/docs/spec/runtime-hook-surface.md +78 -10
- package/docs/standards-register.md +3 -3
- package/docs/survey-utterance-check.md +1 -1
- package/docs/trust-anchor-adoption.md +197 -0
- package/docs/verifiable-trust.md +95 -0
- package/docs/veritas-integration.md +2 -2
- package/docs/workflow-usage-guide.md +69 -0
- package/evals/acceptance/DEMO-false-completion.md +144 -0
- package/evals/acceptance/demo-cast.sh +92 -0
- package/evals/acceptance/demo-false-completion.sh +72 -0
- package/evals/acceptance/demo-real-evidence.sh +104 -0
- package/evals/acceptance/demo.tape +29 -0
- package/evals/acceptance/prove-capture-teeth-declared.sh +335 -0
- package/evals/acceptance/prove-capture-teeth.sh +114 -0
- package/evals/acceptance/prove-teeth.sh +105 -0
- package/evals/ci/antigaming-suite.sh +54 -0
- package/evals/ci/run-baseline.sh +2 -0
- package/evals/fixtures/flow-kit-repository/invalid-missing-extension-asset/flows/review.flow.json +26 -0
- package/evals/fixtures/flow-kit-repository/invalid-missing-extension-asset/kit.json +20 -0
- package/evals/fixtures/flow-kit-repository/valid-unknown-extension/flows/review.flow.json +26 -0
- package/evals/fixtures/flow-kit-repository/valid-unknown-extension/kit.json +18 -0
- package/evals/integration/test_builder_step_producers.sh +379 -0
- package/evals/integration/test_bundle_install.sh +35 -71
- package/evals/integration/test_bundle_lifecycle.sh +39 -2
- package/evals/integration/test_captured_fail_reconciliation.sh +820 -0
- package/evals/integration/test_checkpoint_signing.sh +489 -0
- package/evals/integration/test_claim_lookup.sh +352 -0
- package/evals/integration/test_command_log_integrity.sh +275 -0
- package/evals/integration/test_context_map.sh +0 -2
- package/evals/integration/test_dual_emit_flow_step.sh +278 -0
- package/evals/integration/test_enforcer_expects_driven.sh +281 -0
- package/evals/integration/test_evidence_capture_hook.sh +185 -0
- package/evals/integration/test_flow_kit_repository.sh +2 -0
- package/evals/integration/test_flowdef_session_activation.sh +273 -0
- package/evals/integration/test_flowdef_session_history_preservation.sh +250 -0
- package/evals/integration/test_gate_bypass_chain.sh +448 -0
- package/evals/integration/test_gate_lockdown.sh +1137 -0
- package/evals/integration/test_gate_review_inquiry_records.sh +399 -0
- package/evals/integration/test_goal_fit_escape_hatch.sh +73 -0
- package/evals/integration/test_goal_fit_hook.sh +69 -4
- package/evals/integration/test_goal_fit_rederive.sh +263 -0
- package/evals/integration/test_hook_category_behaviors.sh +14 -0
- package/evals/integration/test_install_merge.sh +1176 -0
- package/evals/integration/test_mint_attestation.sh +373 -0
- package/evals/integration/test_phase_map_and_gate_claim.sh +365 -0
- package/evals/integration/test_publish_delivery.sh +269 -0
- package/evals/integration/test_reconcile_soundness.sh +528 -0
- package/evals/integration/test_resolvefirststep_security.sh +208 -0
- package/evals/integration/test_session_resume_roundtrip.sh +286 -0
- package/evals/integration/test_trust_checkpoint.sh +325 -0
- package/evals/integration/test_trust_reconcile.sh +293 -0
- package/evals/integration/test_verify_cli.sh +208 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +549 -34
- package/evals/lib/node.sh +0 -6
- package/evals/run.sh +47 -0
- package/evals/static/test_library_exports.sh +85 -0
- package/evals/static/test_universal_bundles.sh +15 -0
- package/evals/static/test_workflow_skills.sh +6 -13
- package/install.sh +0 -7
- package/integrations/strands-ts/README.md +25 -15
- package/integrations/veritas/flow-agents.adapter.json +1 -2
- package/kits/builder/flows/build.flow.json +59 -12
- package/kits/builder/kit.json +85 -15
- package/kits/builder/skills/continue-work/SKILL.md +116 -0
- package/kits/builder/skills/deliver/SKILL.md +36 -6
- package/kits/builder/skills/design-probe/SKILL.md +28 -0
- package/kits/builder/skills/execute-plan/SKILL.md +9 -1
- package/kits/builder/skills/gate-review/SKILL.md +234 -0
- package/kits/builder/skills/learning-review/SKILL.md +30 -0
- package/kits/builder/skills/pickup-probe/SKILL.md +29 -0
- package/kits/builder/skills/plan-work/SKILL.md +13 -1
- package/kits/builder/skills/pull-work/SKILL.md +19 -0
- package/kits/knowledge/adapters/default-store/index.js +38 -0
- package/kits/knowledge/adapters/flow-runner/index.js +1620 -0
- package/kits/knowledge/adapters/obsidian-store/index.js +36 -6
- package/kits/knowledge/docs/store-contract.md +314 -0
- package/kits/knowledge/evals/audit-freshness/suite.test.js +368 -0
- package/kits/knowledge/evals/canonicalize-category/suite.test.js +383 -0
- package/kits/knowledge/evals/contract-suite/suite.test.js +111 -0
- package/kits/knowledge/evals/detect-contradictions/suite.test.js +324 -0
- package/kits/knowledge/evals/entities/suite.test.js +40 -0
- package/kits/knowledge/evals/glossary-sync/suite.test.js +416 -0
- package/kits/knowledge/evals/hygiene-review/suite.test.js +396 -0
- package/kits/knowledge/evals/retirement/suite.test.js +145 -0
- package/kits/knowledge/flows/audit-freshness.flow.json +44 -0
- package/kits/knowledge/flows/canonicalize-category.flow.json +44 -0
- package/kits/knowledge/flows/detect-contradictions.flow.json +44 -0
- package/kits/knowledge/flows/glossary-sync.flow.json +61 -0
- package/kits/knowledge/flows/hygiene-review.flow.json +43 -0
- package/kits/knowledge/kit.json +51 -1
- package/package.json +13 -4
- package/packaging/conformance/README.md +10 -2
- package/packaging/conformance/fixtures/evidence-capture--allow-records-command.json +29 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-bundle-disputed-claim.json +29 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-capture-contradicts-claimed-pass.json +30 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-mode.json +23 -0
- package/packaging/conformance/fixtures/stop-goal-fit--off-mode.json +24 -0
- package/packaging/conformance/fixtures/stop-goal-fit--warn-active-delivery.json +5 -2
- package/packaging/conformance/fixtures/stop-goal-fit--warn-no-bundle.json +23 -0
- package/packaging/conformance/fixtures/workflow-steering--reground-active-prompt.json +30 -0
- package/packaging/conformance/fixtures/workflow-steering--reground-session-start.json +30 -0
- package/packaging/conformance/run-conformance.js +1 -1
- package/scripts/README.md +2 -1
- package/scripts/build-universal-bundles.js +0 -1
- package/scripts/ci/mint-attestation.js +221 -0
- package/scripts/ci/trust-reconcile.js +545 -0
- package/scripts/hooks/config-protection.js +423 -1
- package/scripts/hooks/evidence-capture.js +348 -0
- package/scripts/hooks/lib/liveness-read.js +113 -0
- package/scripts/hooks/run-hook.js +6 -1
- package/scripts/hooks/stop-goal-fit.js +1471 -79
- package/scripts/hooks/workflow-steering.js +135 -5
- package/scripts/install-codex-home.sh +39 -0
- package/scripts/install-merge.js +330 -0
- package/src/cli/init.ts +218 -20
- package/src/cli/validate-workflow-artifacts.ts +18 -2
- package/src/cli/verify.ts +100 -0
- package/src/cli/workflow-sidecar.ts +2093 -84
- package/src/cli.ts +2 -3
- package/src/index.ts +53 -0
- package/src/lib/flow-resolver.ts +284 -0
- package/src/tools/build-universal-bundles.ts +34 -21
- package/src/tools/generate-context-map.ts +3 -17
- package/src/tools/validate-source-tree.ts +44 -104
- package/tsconfig.json +1 -0
- package/build/src/tools/filter-installed-packs.js +0 -135
- package/packaging/packs.json +0 -49
- package/scripts/filter-installed-packs.js +0 -2
- package/src/tools/filter-installed-packs.ts +0 -132
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { validateKitRepository as validateFlowKitRepository } from "../flow-kit/validate.js";
|
|
6
6
|
import { loadJson, readText, rel, root, walkFiles } from "./common.js";
|
|
7
7
|
|
|
8
8
|
class Reporter {
|
|
@@ -11,14 +11,10 @@ class Reporter {
|
|
|
11
11
|
check(condition: boolean, message: string): void { if (!condition) this.fail(message); }
|
|
12
12
|
}
|
|
13
13
|
const manifestPath = path.join(root, "packaging/manifest.json");
|
|
14
|
-
const packsPath = path.join(root, "packaging/packs.json");
|
|
15
14
|
const kitsCatalogPath = path.join(root, "kits/catalog.json");
|
|
16
15
|
const flowRoot = process.env.FLOW_CLI_ROOT ? path.resolve(process.env.FLOW_CLI_ROOT) : "";
|
|
17
16
|
const flowSchemaPath = flowRoot ? path.join(flowRoot, "schemas", "flow-definition.schema.json") : "";
|
|
18
17
|
const flowCliPath = flowRoot ? ["dist/cli.js", "src/cli.js"].map((candidate) => path.join(flowRoot, candidate)).find((candidate) => fs.existsSync(candidate)) ?? path.join(flowRoot, "dist/cli.js") : "";
|
|
19
|
-
const kitIdRe = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*$/;
|
|
20
|
-
const kitAssetSections = new Set(["skills", "docs", "adapters", "evals", "assets"]);
|
|
21
|
-
const kitTopLevelKeys = new Set(["schema_version", "id", "name", "product_name", "description", "flows", ...kitAssetSections]);
|
|
22
18
|
const textRefExtensions = new Set([".md", ".yaml", ".yml", ".json", ".sh", ".js", ".toml"]);
|
|
23
19
|
const ignoredRefDirs = new Set(["node_modules", "__pycache__", ".pytest_cache", ".cache"]);
|
|
24
20
|
const legacyRefRe = /(?<![A-Za-z0-9_.-])(?:agents|agent-cards|context|evals|lib|powers|prompts|scripts|skills)\/[A-Za-z0-9_./@:+-]+/g;
|
|
@@ -32,10 +28,8 @@ const mirroredFiles = new Map<string, { mirror: string; allowedDifferences: Arra
|
|
|
32
28
|
]);
|
|
33
29
|
const publicScriptWrappers = new Map<string, { target: string; significantLines: string[] }>([
|
|
34
30
|
["scripts/build-universal-bundles.js", { target: "../build/src/tools/build-universal-bundles.js", significantLines: [
|
|
35
|
-
"// Supports FLOW_AGENTS_PACKS through the TypeScript bundle builder.",
|
|
36
31
|
'import("../build/src/tools/build-universal-bundles.js").then(({ main }) => process.exit(main()));',
|
|
37
32
|
] }],
|
|
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))));'] }],
|
|
39
33
|
["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))));'] }],
|
|
40
34
|
["scripts/kit.js", { target: "../build/src/cli/kit.js", significantLines: ['import("../build/src/cli/kit.js").then(({ main }) => main().then((code) => process.exit(code)));'] }],
|
|
41
35
|
["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()));'] }],
|
|
@@ -62,6 +56,7 @@ const hookFilePolicies = new Map<string, { category: string; requiredNeedles: st
|
|
|
62
56
|
["scripts/hooks/codex-telemetry-hook.js", { category: "telemetry shim", requiredNeedles: ["codex", "telemetry"] }],
|
|
63
57
|
["scripts/hooks/run-hook.js", { category: "hook runner", requiredNeedles: ["isHookEnabled", "Path traversal rejected"] }],
|
|
64
58
|
["scripts/hooks/config-protection.js", { category: "policy hook", requiredNeedles: ["Config Protection Hook"] }],
|
|
59
|
+
["scripts/hooks/evidence-capture.js", { category: "policy hook", requiredNeedles: ["Evidence Capture Hook"] }],
|
|
65
60
|
["scripts/hooks/governance-audit.sh", { category: "policy hook", requiredNeedles: ["governance-audit.sh", "audit_emit"] }],
|
|
66
61
|
["scripts/hooks/opencode-hook-adapter.js", { category: "runtime adapter", requiredNeedles: ["opencode", "run-hook.js"] }],
|
|
67
62
|
["scripts/hooks/opencode-telemetry-hook.js", { category: "telemetry shim", requiredNeedles: ["opencode", "telemetry"] }],
|
|
@@ -78,6 +73,7 @@ const hookFilePolicies = new Map<string, { category: string; requiredNeedles: st
|
|
|
78
73
|
["scripts/hooks/desktop-notify.sh", { category: "local notification helper", requiredNeedles: ["desktop-notify.sh", "osascript"] }],
|
|
79
74
|
["scripts/hooks/lib/audit-transport.sh", { category: "shared hook library", requiredNeedles: ["audit_emit"] }],
|
|
80
75
|
["scripts/hooks/lib/hook-flags.js", { category: "shared hook library", requiredNeedles: ["isHookEnabled"] }],
|
|
76
|
+
["scripts/hooks/lib/liveness-read.js", { category: "shared hook library", requiredNeedles: ["freshHolders", "readLivenessEvents"] }],
|
|
81
77
|
["scripts/hooks/lib/patterns.sh", { category: "shared hook library", requiredNeedles: ["_detect_secrets"] }],
|
|
82
78
|
["scripts/hooks/lib/resolve-formatter.js", { category: "shared hook library", requiredNeedles: ["resolveFormatter"] }],
|
|
83
79
|
]);
|
|
@@ -176,99 +172,14 @@ function validateManifest(reporter: Reporter, manifest: any, agentNames: Set<str
|
|
|
176
172
|
for (const dir of manifest.optional_copy_dirs ?? []) if (!fs.existsSync(path.join(root, dir))) console.log(`warning: ${rel(manifestPath)} optional_copy_dirs entry absent: ${dir}`);
|
|
177
173
|
for (const agent of manifest.codex?.excluded_agents ?? []) reporter.check(agentNames.has(agent), `${rel(manifestPath)}: codex excluded agent '${agent}' does not exist`);
|
|
178
174
|
}
|
|
179
|
-
function
|
|
180
|
-
const data = tryLoadJson(packsPath, reporter);
|
|
181
|
-
if (!data || typeof data !== "object") return;
|
|
182
|
-
reporter.check(data.schema_version === "1.0", `${rel(packsPath)}: schema_version must be 1.0`);
|
|
183
|
-
reporter.check(Array.isArray(data.packs) && data.packs.length > 0, `${rel(packsPath)}: packs must be a non-empty list`);
|
|
184
|
-
const skillNames = new Set(fs.readdirSync(path.join(root, "skills")).filter((name) => fs.existsSync(path.join(root, "skills", name, "SKILL.md"))));
|
|
185
|
-
const powerNames = new Set(fs.readdirSync(path.join(root, "powers")).filter((name) => fs.existsSync(path.join(root, "powers", name, "POWER.md"))));
|
|
186
|
-
const names = new Set<string>(); const defaults = new Set<string>(); const assigned = { skills: new Set<string>(), agents: new Set<string>(), powers: new Set<string>() };
|
|
187
|
-
(Array.isArray(data.packs) ? data.packs : []).forEach((pack: any, index: number) => {
|
|
188
|
-
const name = pack?.name;
|
|
189
|
-
if (typeof name !== "string" || !/^[a-z][a-z0-9-]*$/.test(name)) { reporter.fail(`${rel(packsPath)}: packs[${index}].name must be a kebab-case string`); return; }
|
|
190
|
-
if (names.has(name)) reporter.fail(`${rel(packsPath)}: duplicate pack name '${name}'`);
|
|
191
|
-
names.add(name); if (pack.default === true) defaults.add(name);
|
|
192
|
-
reporter.check(typeof pack.description === "string" && !!pack.description, `${rel(packsPath)}: pack '${name}' missing description`);
|
|
193
|
-
for (const [field, available] of [["skills", skillNames], ["agents", agentNames], ["powers", powerNames]] as const) {
|
|
194
|
-
const values = pack[field] ?? [];
|
|
195
|
-
reporter.check(Array.isArray(values), `${rel(packsPath)}: pack '${name}' .${field} must be a list`);
|
|
196
|
-
const seen = new Set<string>();
|
|
197
|
-
for (const value of Array.isArray(values) ? values : []) {
|
|
198
|
-
if (typeof value !== "string") { reporter.fail(`${rel(packsPath)}: pack '${name}' .${field} entry is not a string`); continue; }
|
|
199
|
-
if (seen.has(value)) reporter.fail(`${rel(packsPath)}: pack '${name}' has duplicate ${field} entry '${value}'`);
|
|
200
|
-
seen.add(value); assigned[field].add(value);
|
|
201
|
-
reporter.check(available.has(value), `${rel(packsPath)}: pack '${name}' references missing ${field.slice(0, -1)} '${value}'`);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
reporter.check(defaults.has("core"), `${rel(packsPath)}: core pack must be default`);
|
|
206
|
-
const missingSkills = [...skillNames].filter((name) => !assigned.skills.has(name)).sort();
|
|
207
|
-
reporter.check(missingSkills.length === 0, `${rel(packsPath)}: skills missing from all packs: ${missingSkills.join(", ")}`);
|
|
208
|
-
}
|
|
209
|
-
function safeLocalPath(baseDir: string, pathText: unknown, label: string, reporter: Reporter): string | undefined {
|
|
210
|
-
if (typeof pathText !== "string" || !pathText) { reporter.fail(`${label} must be a non-empty relative path`); return undefined; }
|
|
211
|
-
if (path.isAbsolute(pathText)) { reporter.fail(`${label} must be relative; absolute paths are not allowed`); return undefined; }
|
|
212
|
-
if (pathText.split(/[\\/]/).includes("..")) { reporter.fail(`${label} must stay inside the kit directory; '..' path traversal is not allowed`); return undefined; }
|
|
213
|
-
return path.join(baseDir, pathText);
|
|
214
|
-
}
|
|
215
|
-
function validateFlowDefinitionShape(file: string, data: any, reporter: Reporter): void {
|
|
216
|
-
const localCli = flowCliPath;
|
|
217
|
-
if (fs.existsSync(localCli)) {
|
|
218
|
-
const result = spawnSync("node", [localCli, "validate-definition", file, "--json"], { encoding: "utf8" });
|
|
219
|
-
if (result.status !== 0) reporter.fail(`${rel(file)}: Flow validation failed: ${(result.stderr || result.stdout).trim()}`);
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
if (!data || typeof data !== "object") { reporter.fail(`${rel(file)}: Flow Definition must be an object`); return; }
|
|
223
|
-
for (const key of ["id", "version", "steps", "gates"]) reporter.check(key in data, `${rel(file)}: missing .${key}`);
|
|
224
|
-
}
|
|
225
|
-
function validateKitRepository(kitDir: string, reporter: Reporter): void {
|
|
175
|
+
async function validateKitRepository(kitDir: string, reporter: Reporter): Promise<void> {
|
|
226
176
|
if (!fs.existsSync(kitDir) || !fs.statSync(kitDir).isDirectory()) { reporter.fail(`${rel(kitDir)}: kit directory does not exist`); return; }
|
|
227
177
|
const kitJson = path.join(kitDir, "kit.json");
|
|
228
178
|
reporter.check(fs.existsSync(kitJson), `${rel(kitDir)}: missing kit.json at repository root`);
|
|
229
179
|
if (!fs.existsSync(kitJson)) return;
|
|
230
|
-
const
|
|
231
|
-
if (!data || typeof data !== "object") return;
|
|
232
|
-
const unknownKeys = Object.keys(data).filter((key) => !kitTopLevelKeys.has(key)).sort();
|
|
233
|
-
if (unknownKeys.length) reporter.fail(`${rel(kitJson)}: unsupported fields ${unknownKeys.join(", ")}; remove them or add them to the Flow Kit Repository contract`);
|
|
234
|
-
reporter.check(data.schema_version === "1.0", `${rel(kitJson)}: .schema_version must be "1.0"`);
|
|
235
|
-
reporter.check(typeof data.id === "string" && kitIdRe.test(data.id), `${rel(kitJson)}: .id must be a stable kebab-case string`);
|
|
236
|
-
reporter.check(typeof data.name === "string" && !!data.name.trim(), `${rel(kitJson)}: .name must be a non-empty string`);
|
|
237
|
-
for (const section of [...kitAssetSections].sort()) if (section in data) {
|
|
238
|
-
if (!Array.isArray(data[section])) { reporter.fail(`${rel(kitJson)}: .${section} must be a list of relative asset paths or objects with path`); continue; }
|
|
239
|
-
const seenPaths = new Set<string>(); const seenIds = new Set<string>();
|
|
240
|
-
data[section].forEach((entry: any, index: number) => {
|
|
241
|
-
const pathValue = typeof entry === "string" ? entry : entry?.path;
|
|
242
|
-
const assetId = typeof entry === "object" ? entry.id : undefined;
|
|
243
|
-
if (typeof entry === "object") {
|
|
244
|
-
const unknown = Object.keys(entry).filter((key) => !["id", "path", "description"].includes(key)).sort();
|
|
245
|
-
if (unknown.length) reporter.fail(`${rel(kitJson)}: ${section}[${index}] has unsupported fields ${unknown.join(", ")}; use id, path, or description`);
|
|
246
|
-
}
|
|
247
|
-
if (assetId !== undefined && (typeof assetId !== "string" || !kitIdRe.test(assetId))) reporter.fail(`${rel(kitJson)}: ${section}[${index}].id must be a stable dot/kebab-case string`);
|
|
248
|
-
const assetPath = safeLocalPath(kitDir, pathValue, `${rel(kitJson)}: ${section}[${index}].path`, reporter);
|
|
249
|
-
if (!assetPath) return;
|
|
250
|
-
if (seenPaths.has(String(pathValue))) reporter.fail(`${rel(kitJson)}: ${section}[${index}].path duplicates '${pathValue}'; declare each asset once`);
|
|
251
|
-
seenPaths.add(String(pathValue));
|
|
252
|
-
if (typeof assetId === "string") { if (seenIds.has(assetId)) reporter.fail(`${rel(kitJson)}: ${section}[${index}].id duplicates '${assetId}'; use a unique asset id`); seenIds.add(assetId); }
|
|
253
|
-
reporter.check(fs.existsSync(assetPath), `${rel(kitJson)}: ${section}[${index}].path points at missing asset: ${pathValue}; add the file or remove the entry`);
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
if (!Array.isArray(data.flows) || !data.flows.length) { reporter.fail(`${rel(kitJson)}: .flows must be a non-empty list; add at least one Flow Definition entry`); return; }
|
|
257
|
-
const seenIds = new Set<string>(); const seenPaths = new Set<string>();
|
|
258
|
-
data.flows.forEach((flow: any, index: number) => {
|
|
259
|
-
if (!flow || typeof flow !== "object") { reporter.fail(`${rel(kitJson)}: flows[${index}] must be an object with id and path`); return; }
|
|
260
|
-
if (typeof flow.id !== "string" || !kitIdRe.test(flow.id)) reporter.fail(`${rel(kitJson)}: flows[${index}].id must be a stable dot/kebab-case string`);
|
|
261
|
-
else if (seenIds.has(flow.id)) reporter.fail(`${rel(kitJson)}: flows[${index}].id duplicates '${flow.id}'; use a unique Flow id`);
|
|
262
|
-
else seenIds.add(flow.id);
|
|
263
|
-
const flowPath = safeLocalPath(kitDir, flow.path, `${rel(kitJson)}: flows[${index}].path`, reporter);
|
|
264
|
-
if (!flowPath) return;
|
|
265
|
-
if (seenPaths.has(String(flow.path))) { reporter.fail(`${rel(kitJson)}: flows[${index}].path duplicates '${flow.path}'; declare each Flow Definition once`); return; }
|
|
266
|
-
seenPaths.add(String(flow.path));
|
|
267
|
-
reporter.check(fs.existsSync(flowPath), `${rel(kitJson)}: flows[${index}].path points at missing Flow Definition: ${flow.path}; add the file or fix the path`);
|
|
268
|
-
if (fs.existsSync(flowPath)) validateFlowDefinitionShape(flowPath, tryLoadJson(flowPath, reporter), reporter);
|
|
269
|
-
});
|
|
180
|
+
for (const error of await validateFlowKitRepository(kitDir)) reporter.fail(error);
|
|
270
181
|
}
|
|
271
|
-
function validateKits(reporter: Reporter): void {
|
|
182
|
+
async function validateKits(reporter: Reporter): Promise<void> {
|
|
272
183
|
reporter.check(fs.existsSync(path.join(root, "kits")), "kits directory missing");
|
|
273
184
|
const catalog = tryLoadJson(kitsCatalogPath, reporter);
|
|
274
185
|
const kits = catalog?.kits;
|
|
@@ -277,14 +188,14 @@ function validateKits(reporter: Reporter): void {
|
|
|
277
188
|
const localCli = flowCliPath;
|
|
278
189
|
if (flowSchemaPath && fs.existsSync(flowSchemaPath)) console.log(fs.existsSync(localCli) ? `info: validating kit Flow Definitions with Flow CLI at ${localCli}` : `warning: Flow validator unavailable; source-tree check only verifies Flow Definition top-level shape`);
|
|
279
190
|
else console.log("warning: Flow schema not configured; source-tree check only verifies Flow Definition top-level shape. Set FLOW_CLI_ROOT to enable Flow CLI validation. Container validation (kit.json core fields) will delegate to 'flow validate-kit' from @kontourai/flow when FLOW_CLI_ROOT is available.");
|
|
280
|
-
|
|
191
|
+
for (const [index, entry] of kits.entries()) {
|
|
281
192
|
const kitText = typeof entry === "string" ? entry : ["path", "directory", "dir", "id", "name"].map((key) => entry?.[key]).find((value) => typeof value === "string" && value);
|
|
282
|
-
if (!kitText) { reporter.fail(`${rel(kitsCatalogPath)}: kits[${index}] missing path, directory, dir, id, or name`);
|
|
193
|
+
if (!kitText) { reporter.fail(`${rel(kitsCatalogPath)}: kits[${index}] missing path, directory, dir, id, or name`); continue; }
|
|
283
194
|
const kitRef = String(kitText).startsWith("kits/") ? path.join(root, kitText) : path.join(root, "kits", kitText);
|
|
284
195
|
const kitDir = path.basename(kitRef) === "kit.json" ? path.dirname(kitRef) : kitRef;
|
|
285
196
|
reporter.check(fs.existsSync(kitDir) && fs.statSync(kitDir).isDirectory(), `${rel(kitsCatalogPath)}: kits[${index}] points at missing kit folder: ${kitText}`);
|
|
286
|
-
validateKitRepository(kitDir, reporter);
|
|
287
|
-
}
|
|
197
|
+
await validateKitRepository(kitDir, reporter);
|
|
198
|
+
}
|
|
288
199
|
}
|
|
289
200
|
function validateAgentPaths(reporter: Reporter, manifest: any): void {
|
|
290
201
|
for (const file of walkFiles(path.join(root, "agents")).filter((item) => item.endsWith(".json"))) {
|
|
@@ -372,6 +283,35 @@ function validatePublicScriptWrappers(reporter: Reporter): void {
|
|
|
372
283
|
reporter.check(JSON.stringify(significantLines) === JSON.stringify(policy.significantLines), `${file}: public wrapper must match the exact thin launcher body for ${policy.target}`);
|
|
373
284
|
}
|
|
374
285
|
}
|
|
286
|
+
function validateAdrNumbers(reporter: Reporter): void {
|
|
287
|
+
// Each ADR (a docs/adr file with an `# ADR NNNN:` heading) must own a unique
|
|
288
|
+
// number, and its filename prefix must match that number. Companion/index docs
|
|
289
|
+
// without an ADR heading (e.g. a numbered skill-audit tied to an ADR) are
|
|
290
|
+
// intentionally skipped. Guards against concurrent number collisions like the
|
|
291
|
+
// duplicate ADR 0014 from PRs #180/#172.
|
|
292
|
+
const adrDir = path.join(root, "docs/adr");
|
|
293
|
+
if (!fs.existsSync(adrDir)) return;
|
|
294
|
+
const byNumber = new Map<string, string[]>();
|
|
295
|
+
for (const file of walkFiles(adrDir)) {
|
|
296
|
+
if (path.extname(file) !== ".md") continue;
|
|
297
|
+
const heading = readText(file).match(/^#\s+ADR\s+(\d{4}):/m);
|
|
298
|
+
if (!heading) continue; // not an ADR decision doc
|
|
299
|
+
const num = heading[1];
|
|
300
|
+
reporter.check(
|
|
301
|
+
path.basename(file).startsWith(`${num}-`),
|
|
302
|
+
`${rel(file)}: ADR heading number ${num} does not match the filename prefix`,
|
|
303
|
+
);
|
|
304
|
+
const list = byNumber.get(num) ?? [];
|
|
305
|
+
list.push(rel(file));
|
|
306
|
+
byNumber.set(num, list);
|
|
307
|
+
}
|
|
308
|
+
for (const [num, files] of byNumber) {
|
|
309
|
+
reporter.check(
|
|
310
|
+
files.length === 1,
|
|
311
|
+
`docs/adr: duplicate ADR number ${num} — ${files.join(", ")}. ADR numbers must be unique; renumber one.`,
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
375
315
|
function validateHookInventory(reporter: Reporter): void {
|
|
376
316
|
const readme = readText(path.join(root, "scripts/README.md"));
|
|
377
317
|
const hookFiles = walkFiles(path.join(root, "scripts/hooks"))
|
|
@@ -483,7 +423,7 @@ function validateNoFirstPartyPythonCommands(reporter: Reporter): void {
|
|
|
483
423
|
reporter.fail(`${relative}: direct first-party Python command reference is not allowed; use npm/flow-agents TypeScript commands`);
|
|
484
424
|
}
|
|
485
425
|
}
|
|
486
|
-
export function main(argv = process.argv.slice(2)): number {
|
|
426
|
+
export async function main(argv = process.argv.slice(2)): Promise<number> {
|
|
487
427
|
const kitIndex = argv.indexOf("--kit");
|
|
488
428
|
if (kitIndex >= 0) {
|
|
489
429
|
const kitDir = argv[kitIndex + 1];
|
|
@@ -492,7 +432,7 @@ export function main(argv = process.argv.slice(2)): number {
|
|
|
492
432
|
const localCli = flowCliPath;
|
|
493
433
|
if (flowSchemaPath && fs.existsSync(flowSchemaPath) && fs.existsSync(localCli)) console.log(`info: validating kit Flow Definitions with Flow CLI at ${localCli}`);
|
|
494
434
|
else console.log("warning: Flow validation surface unavailable; local kit check uses the minimal Flow Definition fallback");
|
|
495
|
-
validateKitRepository(path.resolve(kitDir), reporter);
|
|
435
|
+
await validateKitRepository(path.resolve(kitDir), reporter);
|
|
496
436
|
if (reporter.errors.length) { console.log("Flow Kit repository validation failed:"); for (const error of reporter.errors) console.log(` - ${error}`); return 1; }
|
|
497
437
|
console.log("Flow Kit repository validation passed."); return 0;
|
|
498
438
|
}
|
|
@@ -502,14 +442,14 @@ export function main(argv = process.argv.slice(2)): number {
|
|
|
502
442
|
validateAgentCards(reporter, agentNames);
|
|
503
443
|
validatePowers(reporter);
|
|
504
444
|
validateManifest(reporter, manifest, agentNames);
|
|
505
|
-
|
|
506
|
-
validateKits(reporter);
|
|
445
|
+
await validateKits(reporter);
|
|
507
446
|
validateAgentPaths(reporter, manifest);
|
|
508
447
|
validateLegacyRefs(reporter);
|
|
509
448
|
validateMirrors(reporter);
|
|
510
449
|
validateUsageFeedbackFiles(reporter);
|
|
511
450
|
validatePublicScriptWrappers(reporter);
|
|
512
451
|
validateHookInventory(reporter);
|
|
452
|
+
validateAdrNumbers(reporter);
|
|
513
453
|
validateFixtureOwnership(reporter);
|
|
514
454
|
validatePackageCommandSurface(reporter);
|
|
515
455
|
validateNoFirstPartyPythonFiles(reporter);
|
|
@@ -523,4 +463,4 @@ export function main(argv = process.argv.slice(2)): number {
|
|
|
523
463
|
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
524
464
|
const _selfRealPath = (() => { try { return fs.realpathSync(fileURLToPath(import.meta.url)); } catch { return fileURLToPath(import.meta.url); } })();
|
|
525
465
|
const _argv1RealPath = (() => { try { return fs.realpathSync(process.argv[1]); } catch { return process.argv[1]; } })();
|
|
526
|
-
if (_selfRealPath === _argv1RealPath) { process.exitCode =
|
|
466
|
+
if (_selfRealPath === _argv1RealPath) { main().then((code) => { process.exitCode = code; }); }
|
package/tsconfig.json
CHANGED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { loadJson, writeText } from "./common.js";
|
|
6
|
-
function splitPacks(value) {
|
|
7
|
-
return new Set(value.split(",").map((item) => item.trim()).filter(Boolean));
|
|
8
|
-
}
|
|
9
|
-
function namesFor(packs) {
|
|
10
|
-
return new Set((packs.packs ?? []).map((pack) => String(pack.name ?? "")));
|
|
11
|
-
}
|
|
12
|
-
function entries(pack, field) {
|
|
13
|
-
const value = pack[field];
|
|
14
|
-
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
15
|
-
}
|
|
16
|
-
function assertSafeName(name, label) {
|
|
17
|
-
if (!/^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*$/.test(name))
|
|
18
|
-
throw new Error(`${label} contains unsafe pack member name: ${name}`);
|
|
19
|
-
}
|
|
20
|
-
function assertContained(rootDir, target) {
|
|
21
|
-
const rootReal = fs.realpathSync(rootDir);
|
|
22
|
-
const parentReal = fs.realpathSync(path.dirname(target));
|
|
23
|
-
const resolved = path.resolve(parentReal, path.basename(target));
|
|
24
|
-
const relative = path.relative(rootReal, resolved);
|
|
25
|
-
if (!relative || relative.startsWith("..") || path.isAbsolute(relative))
|
|
26
|
-
throw new Error(`refusing to remove path outside install root: ${target}`);
|
|
27
|
-
if (fs.existsSync(target)) {
|
|
28
|
-
const targetReal = fs.realpathSync(target);
|
|
29
|
-
const targetRelative = path.relative(rootReal, targetReal);
|
|
30
|
-
if (!targetRelative || targetRelative.startsWith("..") || path.isAbsolute(targetRelative))
|
|
31
|
-
throw new Error(`refusing to remove path outside install root: ${target}`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
function packSelection(packs, requested) {
|
|
35
|
-
const selectedNames = new Set();
|
|
36
|
-
for (const pack of packs.packs ?? [])
|
|
37
|
-
if (pack.default === true)
|
|
38
|
-
selectedNames.add(String(pack.name));
|
|
39
|
-
for (const name of requested)
|
|
40
|
-
selectedNames.add(name);
|
|
41
|
-
const unknown = [...selectedNames].filter((name) => !namesFor(packs).has(name)).sort();
|
|
42
|
-
if (unknown.length)
|
|
43
|
-
throw new Error(`unknown pack(s): ${unknown.join(", ")}`);
|
|
44
|
-
const selected = { skills: new Set(), agents: new Set(), powers: new Set() };
|
|
45
|
-
for (const pack of packs.packs ?? []) {
|
|
46
|
-
if (!selectedNames.has(String(pack.name)))
|
|
47
|
-
continue;
|
|
48
|
-
for (const field of Object.keys(selected)) {
|
|
49
|
-
for (const item of entries(pack, field))
|
|
50
|
-
selected[field].add(item);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return [selectedNames, selected];
|
|
54
|
-
}
|
|
55
|
-
function allPackMembers(packs, field) {
|
|
56
|
-
const values = new Set();
|
|
57
|
-
for (const pack of packs.packs ?? [])
|
|
58
|
-
for (const item of entries(pack, field))
|
|
59
|
-
values.add(item);
|
|
60
|
-
return values;
|
|
61
|
-
}
|
|
62
|
-
function removePath(rootDir, target, dryRun) {
|
|
63
|
-
if (!fs.existsSync(target))
|
|
64
|
-
return false;
|
|
65
|
-
assertContained(rootDir, target);
|
|
66
|
-
if (dryRun) {
|
|
67
|
-
console.log(`would remove ${target}`);
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
fs.rmSync(target, { recursive: true, force: true });
|
|
71
|
-
console.log(`removed ${target}`);
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
function pruneNamedDirs(rootDir, parent, known, keep, dryRun) {
|
|
75
|
-
let removed = 0;
|
|
76
|
-
for (const name of [...known].filter((name) => !keep.has(name)).sort()) {
|
|
77
|
-
assertSafeName(name, parent);
|
|
78
|
-
if (removePath(rootDir, path.join(rootDir, parent, name), dryRun))
|
|
79
|
-
removed += 1;
|
|
80
|
-
}
|
|
81
|
-
return removed;
|
|
82
|
-
}
|
|
83
|
-
function pruneAgentFiles(rootDir, parent, suffix, known, keep, dryRun) {
|
|
84
|
-
let removed = 0;
|
|
85
|
-
for (const name of [...known].filter((name) => !keep.has(name)).sort()) {
|
|
86
|
-
assertSafeName(name, parent);
|
|
87
|
-
if (removePath(rootDir, path.join(rootDir, parent, `${name}${suffix}`), dryRun))
|
|
88
|
-
removed += 1;
|
|
89
|
-
}
|
|
90
|
-
return removed;
|
|
91
|
-
}
|
|
92
|
-
export function main(argv = process.argv.slice(2)) {
|
|
93
|
-
const args = argv;
|
|
94
|
-
const dryRun = args.includes("--dry-run");
|
|
95
|
-
const rootArg = args.find((arg) => !arg.startsWith("--"));
|
|
96
|
-
const packsFlag = args.indexOf("--packs");
|
|
97
|
-
if (!rootArg || packsFlag < 0 || !args[packsFlag + 1]) {
|
|
98
|
-
console.error("usage: filter-installed-packs <root> --packs <packs> [--dry-run]");
|
|
99
|
-
return 2;
|
|
100
|
-
}
|
|
101
|
-
const rootDir = path.resolve(rootArg);
|
|
102
|
-
const packsPath = path.join(rootDir, "packaging", "packs.json");
|
|
103
|
-
if (!fs.existsSync(packsPath))
|
|
104
|
-
throw new Error(`pack manifest not found: ${packsPath}`);
|
|
105
|
-
const packs = loadJson(packsPath);
|
|
106
|
-
const [selectedNames, selected] = packSelection(packs, splitPacks(args[packsFlag + 1]));
|
|
107
|
-
let removed = 0;
|
|
108
|
-
removed += pruneNamedDirs(rootDir, "skills", allPackMembers(packs, "skills"), selected.skills, dryRun);
|
|
109
|
-
removed += pruneNamedDirs(rootDir, ".claude/skills", allPackMembers(packs, "skills"), selected.skills, dryRun);
|
|
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);
|
|
113
|
-
removed += pruneNamedDirs(rootDir, "powers", allPackMembers(packs, "powers"), selected.powers, dryRun);
|
|
114
|
-
removed += pruneAgentFiles(rootDir, "agents", ".json", allPackMembers(packs, "agents"), selected.agents, dryRun);
|
|
115
|
-
removed += pruneAgentFiles(rootDir, ".claude/agents", ".md", allPackMembers(packs, "agents"), selected.agents, dryRun);
|
|
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);
|
|
118
|
-
const summary = {
|
|
119
|
-
selected_packs: [...selectedNames].sort(),
|
|
120
|
-
removed_entries: removed,
|
|
121
|
-
kept: Object.fromEntries(Object.entries(selected).map(([field, values]) => [field, [...values].sort()])),
|
|
122
|
-
};
|
|
123
|
-
if (!dryRun)
|
|
124
|
-
writeText(path.join(rootDir, ".flow-agents/installed-packs.json"), `${JSON.stringify(summary, null, 2)}\n`);
|
|
125
|
-
console.log(JSON.stringify(summary, null, 2));
|
|
126
|
-
return 0;
|
|
127
|
-
}
|
|
128
|
-
if (process.argv[1] && fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(path.resolve(process.argv[1])))
|
|
129
|
-
try {
|
|
130
|
-
process.exit(main());
|
|
131
|
-
}
|
|
132
|
-
catch (error) {
|
|
133
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
134
|
-
process.exit(1);
|
|
135
|
-
}
|
package/packaging/packs.json
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schema_version": "1.0",
|
|
3
|
-
"packs": [
|
|
4
|
-
{
|
|
5
|
-
"name": "core",
|
|
6
|
-
"default": true,
|
|
7
|
-
"description": "Small default surface for reliable coding and workflow execution.",
|
|
8
|
-
"skills": [
|
|
9
|
-
"search-first",
|
|
10
|
-
"browser-test"
|
|
11
|
-
],
|
|
12
|
-
"agents": [
|
|
13
|
-
"tool-planner",
|
|
14
|
-
"tool-worker",
|
|
15
|
-
"tool-verifier",
|
|
16
|
-
"tool-code-reviewer",
|
|
17
|
-
"tool-playwright"
|
|
18
|
-
],
|
|
19
|
-
"powers": [
|
|
20
|
-
"playwright"
|
|
21
|
-
]
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"name": "development",
|
|
25
|
-
"default": false,
|
|
26
|
-
"description": "Development workflow depth for backlog, release, dependency, GitHub, TDD, and frontend work.",
|
|
27
|
-
"skills": [
|
|
28
|
-
"dependency-update",
|
|
29
|
-
"eval-rebuild",
|
|
30
|
-
"github-cli",
|
|
31
|
-
"agentic-engineering"
|
|
32
|
-
],
|
|
33
|
-
"agents": [
|
|
34
|
-
"dev",
|
|
35
|
-
"tool-dependencies-updater",
|
|
36
|
-
"tool-security-reviewer",
|
|
37
|
-
"tool-explore-config",
|
|
38
|
-
"tool-explore-deps",
|
|
39
|
-
"tool-explore-entry",
|
|
40
|
-
"tool-explore-patterns",
|
|
41
|
-
"tool-explore-structure",
|
|
42
|
-
"tool-explore-tests"
|
|
43
|
-
],
|
|
44
|
-
"powers": [
|
|
45
|
-
"dependency-checker"
|
|
46
|
-
]
|
|
47
|
-
}
|
|
48
|
-
]
|
|
49
|
-
}
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { loadJson, writeText } from "./common.js";
|
|
6
|
-
|
|
7
|
-
type PacksManifest = { packs?: Array<Record<string, unknown>> };
|
|
8
|
-
type Selection = Record<"skills" | "agents" | "powers", Set<string>>;
|
|
9
|
-
|
|
10
|
-
function splitPacks(value: string): Set<string> {
|
|
11
|
-
return new Set(value.split(",").map((item) => item.trim()).filter(Boolean));
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function namesFor(packs: PacksManifest): Set<string> {
|
|
15
|
-
return new Set((packs.packs ?? []).map((pack) => String(pack.name ?? "")));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function entries(pack: Record<string, unknown>, field: keyof Selection): string[] {
|
|
19
|
-
const value = pack[field];
|
|
20
|
-
return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string") : [];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function assertSafeName(name: string, label: string): void {
|
|
24
|
-
if (!/^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*$/.test(name)) throw new Error(`${label} contains unsafe pack member name: ${name}`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function assertContained(rootDir: string, target: string): void {
|
|
28
|
-
const rootReal = fs.realpathSync(rootDir);
|
|
29
|
-
const parentReal = fs.realpathSync(path.dirname(target));
|
|
30
|
-
const resolved = path.resolve(parentReal, path.basename(target));
|
|
31
|
-
const relative = path.relative(rootReal, resolved);
|
|
32
|
-
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`refusing to remove path outside install root: ${target}`);
|
|
33
|
-
if (fs.existsSync(target)) {
|
|
34
|
-
const targetReal = fs.realpathSync(target);
|
|
35
|
-
const targetRelative = path.relative(rootReal, targetReal);
|
|
36
|
-
if (!targetRelative || targetRelative.startsWith("..") || path.isAbsolute(targetRelative)) throw new Error(`refusing to remove path outside install root: ${target}`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function packSelection(packs: PacksManifest, requested: Set<string>): [Set<string>, Selection] {
|
|
41
|
-
const selectedNames = new Set<string>();
|
|
42
|
-
for (const pack of packs.packs ?? []) if (pack.default === true) selectedNames.add(String(pack.name));
|
|
43
|
-
for (const name of requested) selectedNames.add(name);
|
|
44
|
-
const unknown = [...selectedNames].filter((name) => !namesFor(packs).has(name)).sort();
|
|
45
|
-
if (unknown.length) throw new Error(`unknown pack(s): ${unknown.join(", ")}`);
|
|
46
|
-
const selected: Selection = { skills: new Set(), agents: new Set(), powers: new Set() };
|
|
47
|
-
for (const pack of packs.packs ?? []) {
|
|
48
|
-
if (!selectedNames.has(String(pack.name))) continue;
|
|
49
|
-
for (const field of Object.keys(selected) as Array<keyof Selection>) {
|
|
50
|
-
for (const item of entries(pack, field)) selected[field].add(item);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return [selectedNames, selected];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function allPackMembers(packs: PacksManifest, field: keyof Selection): Set<string> {
|
|
57
|
-
const values = new Set<string>();
|
|
58
|
-
for (const pack of packs.packs ?? []) for (const item of entries(pack, field)) values.add(item);
|
|
59
|
-
return values;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function removePath(rootDir: string, target: string, dryRun: boolean): boolean {
|
|
63
|
-
if (!fs.existsSync(target)) return false;
|
|
64
|
-
assertContained(rootDir, target);
|
|
65
|
-
if (dryRun) {
|
|
66
|
-
console.log(`would remove ${target}`);
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
fs.rmSync(target, { recursive: true, force: true });
|
|
70
|
-
console.log(`removed ${target}`);
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function pruneNamedDirs(rootDir: string, parent: string, known: Set<string>, keep: Set<string>, dryRun: boolean): number {
|
|
75
|
-
let removed = 0;
|
|
76
|
-
for (const name of [...known].filter((name) => !keep.has(name)).sort()) {
|
|
77
|
-
assertSafeName(name, parent);
|
|
78
|
-
if (removePath(rootDir, path.join(rootDir, parent, name), dryRun)) removed += 1;
|
|
79
|
-
}
|
|
80
|
-
return removed;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function pruneAgentFiles(rootDir: string, parent: string, suffix: string, known: Set<string>, keep: Set<string>, dryRun: boolean): number {
|
|
84
|
-
let removed = 0;
|
|
85
|
-
for (const name of [...known].filter((name) => !keep.has(name)).sort()) {
|
|
86
|
-
assertSafeName(name, parent);
|
|
87
|
-
if (removePath(rootDir, path.join(rootDir, parent, `${name}${suffix}`), dryRun)) removed += 1;
|
|
88
|
-
}
|
|
89
|
-
return removed;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function main(argv = process.argv.slice(2)): number {
|
|
93
|
-
const args = argv;
|
|
94
|
-
const dryRun = args.includes("--dry-run");
|
|
95
|
-
const rootArg = args.find((arg) => !arg.startsWith("--"));
|
|
96
|
-
const packsFlag = args.indexOf("--packs");
|
|
97
|
-
if (!rootArg || packsFlag < 0 || !args[packsFlag + 1]) {
|
|
98
|
-
console.error("usage: filter-installed-packs <root> --packs <packs> [--dry-run]");
|
|
99
|
-
return 2;
|
|
100
|
-
}
|
|
101
|
-
const rootDir = path.resolve(rootArg);
|
|
102
|
-
const packsPath = path.join(rootDir, "packaging", "packs.json");
|
|
103
|
-
if (!fs.existsSync(packsPath)) throw new Error(`pack manifest not found: ${packsPath}`);
|
|
104
|
-
const packs = loadJson<PacksManifest>(packsPath);
|
|
105
|
-
const [selectedNames, selected] = packSelection(packs, splitPacks(args[packsFlag + 1]));
|
|
106
|
-
let removed = 0;
|
|
107
|
-
removed += pruneNamedDirs(rootDir, "skills", allPackMembers(packs, "skills"), selected.skills, dryRun);
|
|
108
|
-
removed += pruneNamedDirs(rootDir, ".claude/skills", allPackMembers(packs, "skills"), selected.skills, dryRun);
|
|
109
|
-
removed += pruneNamedDirs(rootDir, ".codex/skills", allPackMembers(packs, "skills"), selected.skills, dryRun);
|
|
110
|
-
removed += pruneNamedDirs(rootDir, ".opencode/skills", allPackMembers(packs, "skills"), selected.skills, dryRun);
|
|
111
|
-
removed += pruneNamedDirs(rootDir, ".pi/skills", allPackMembers(packs, "skills"), selected.skills, dryRun);
|
|
112
|
-
removed += pruneNamedDirs(rootDir, "powers", allPackMembers(packs, "powers"), selected.powers, dryRun);
|
|
113
|
-
removed += pruneAgentFiles(rootDir, "agents", ".json", allPackMembers(packs, "agents"), selected.agents, dryRun);
|
|
114
|
-
removed += pruneAgentFiles(rootDir, ".claude/agents", ".md", allPackMembers(packs, "agents"), selected.agents, dryRun);
|
|
115
|
-
removed += pruneAgentFiles(rootDir, ".codex/agents", ".toml", allPackMembers(packs, "agents"), selected.agents, dryRun);
|
|
116
|
-
removed += pruneAgentFiles(rootDir, ".opencode/agents", ".md", allPackMembers(packs, "agents"), selected.agents, dryRun);
|
|
117
|
-
const summary = {
|
|
118
|
-
selected_packs: [...selectedNames].sort(),
|
|
119
|
-
removed_entries: removed,
|
|
120
|
-
kept: Object.fromEntries(Object.entries(selected).map(([field, values]) => [field, [...values].sort()])),
|
|
121
|
-
};
|
|
122
|
-
if (!dryRun) writeText(path.join(rootDir, ".flow-agents/installed-packs.json"), `${JSON.stringify(summary, null, 2)}\n`);
|
|
123
|
-
console.log(JSON.stringify(summary, null, 2));
|
|
124
|
-
return 0;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (process.argv[1] && fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(path.resolve(process.argv[1]))) try {
|
|
128
|
-
process.exit(main());
|
|
129
|
-
} catch (error) {
|
|
130
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
131
|
-
process.exit(1);
|
|
132
|
-
}
|