@llblab/pi-actors 0.45.1 → 0.46.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/AGENTS.md +9 -7
- package/CHANGELOG.md +8 -1
- package/README.md +26 -12
- package/dist/fixtures/protocol/recipe-summary.json +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/async-runs.d.ts +4 -1
- package/dist/lib/async-runs.js +22 -23
- package/dist/lib/automatic-review-runtime.d.ts +2 -0
- package/dist/lib/automatic-review-runtime.js +12 -5
- package/dist/lib/draft-sleep.js +0 -1
- package/dist/lib/extension-runtime.d.ts +1 -1
- package/dist/lib/extension-runtime.js +8 -2
- package/dist/lib/inspector.js +45 -7
- package/dist/lib/paths.d.ts +0 -1
- package/dist/lib/paths.js +0 -7
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +2 -2
- package/dist/lib/recipes-context.js +2 -2
- package/dist/lib/recipes-references.d.ts +24 -9
- package/dist/lib/recipes-references.js +152 -126
- package/dist/lib/runtime.d.ts +0 -1
- package/dist/lib/runtime.js +1 -3
- package/dist/lib/tools-inspect.d.ts +0 -1
- package/dist/lib/tools-inspect.js +35 -8
- package/dist/lib/tools-local.d.ts +2 -0
- package/dist/lib/tools-local.js +1 -1
- package/dist/lib/tools-spawn.d.ts +2 -0
- package/dist/lib/tools-spawn.js +24 -52
- package/dist/scripts/build-dist.mjs +13 -4
- package/dist/skills/actors/SKILL.md +6 -6
- package/dist/skills/actors/recipes/recipe-validate.json +11 -0
- package/{recipes/utility-coordinator-lock-snapshot.json → dist/skills/actors/recipes/resource-locker-snapshot.json} +1 -3
- package/dist/{recipes → skills/actors/recipes}/resource-locker.json +1 -3
- package/dist/{recipes/utility-run-ops-snapshot.json → skills/actors/recipes/run-ops-snapshot.json} +1 -3
- package/dist/skills/actors/recipes/run-summary.json +9 -0
- package/dist/{scripts/locker.mjs → skills/actors/scripts/resource-locker.mjs} +2 -2
- package/dist/skills/actors/scripts/run-utils.mjs +164 -0
- package/{scripts → dist/skills/actors/scripts}/validate-recipe.mjs +134 -17
- package/dist/skills/artifacts/SKILL.md +16 -0
- package/{recipes/pipeline-artifact-bundle.json → dist/skills/artifacts/recipes/bundle.json} +4 -4
- package/dist/{recipes/utility-artifact-write.json → skills/artifacts/recipes/file-write.json} +1 -3
- package/dist/{recipes/utility-artifact-manifest.json → skills/artifacts/recipes/manifest.json} +1 -3
- package/{recipes/pipeline-artifact-report.json → dist/skills/artifacts/recipes/report.json} +2 -2
- package/dist/{recipes/pipeline-artifact-write.json → skills/artifacts/recipes/write.json} +3 -3
- package/dist/skills/artifacts/scripts/artifact-utils.mjs +63 -0
- package/dist/skills/media/SKILL.md +16 -0
- package/dist/{recipes/pipeline-media-library.json → skills/media/recipes/library.json} +2 -2
- package/{recipes/music-player.json → dist/skills/media/recipes/player.json} +1 -3
- package/dist/{recipes/utility-playlist-build.json → skills/media/recipes/playlist-build.json} +1 -3
- package/dist/skills/media/scripts/media-utils.mjs +47 -0
- package/dist/{scripts → skills/media/scripts}/music-player.mjs +9 -3
- package/dist/skills/project-work/SKILL.md +16 -0
- package/dist/skills/project-work/recipes/changelog-section.json +11 -0
- package/{recipes/pipeline-docs-maintenance.json → dist/skills/project-work/recipes/docs-maintenance.json} +4 -4
- package/dist/skills/project-work/recipes/package-summary.json +9 -0
- package/{recipes/pipeline-release-readiness.json → dist/skills/project-work/recipes/release-readiness.json} +6 -6
- package/dist/{recipes/pipeline-release-summary.json → skills/project-work/recipes/release-summary.json} +5 -5
- package/{recipes/pipeline-repo-health.json → dist/skills/project-work/recipes/repo-health.json} +6 -6
- package/{recipes/pipeline-async-run-ops.json → dist/skills/project-work/recipes/run-ops.json} +3 -3
- package/dist/skills/project-work/recipes/skill-summary.json +11 -0
- package/dist/skills/project-work/scripts/project-utils.mjs +89 -0
- package/dist/skills/recipe-memory/SKILL.md +16 -0
- package/dist/skills/swarm/SKILL.md +4 -4
- package/{recipes/pipeline-architect-coordinator.json → dist/skills/swarm/recipes/architect.json} +5 -5
- package/{recipes/pipeline-checkpoint-continuation.json → dist/skills/swarm/recipes/checkpoint-continuation.json} +3 -3
- package/dist/{recipes/pipeline-development-tasking.json → skills/swarm/recipes/development-tasking.json} +4 -4
- package/dist/{recipes/lens-swarm.json → skills/swarm/recipes/lens-review.json} +1 -1
- package/dist/{recipes/pipeline-quorum-review.json → skills/swarm/recipes/quorum-review.json} +4 -4
- package/{recipes/pipeline-research-synthesis.json → dist/skills/swarm/recipes/research-synthesis.json} +6 -6
- package/{recipes/pipeline-review-readiness.json → dist/skills/swarm/recipes/review-readiness.json} +1 -1
- package/{recipes/subagents-prompts.json → dist/skills/swarm/recipes/subagent-prompts.json} +1 -1
- package/{recipes → dist/skills/swarm/recipes}/subagent-quorum.json +1 -1
- package/dist/{recipes → skills/swarm/recipes}/subagent-review-coordinator.json +6 -6
- package/docs/actor-inspector.md +4 -3
- package/docs/async-runs.md +2 -2
- package/docs/recipe-library.md +23 -23
- package/docs/releasing.md +2 -2
- package/docs/template-recipes.md +30 -14
- package/docs/tool-registry.md +2 -2
- package/fixtures/protocol/recipe-summary.json +1 -1
- package/index.ts +2 -1
- package/lib/async-runs.ts +47 -24
- package/lib/automatic-review-runtime.ts +16 -3
- package/lib/draft-sleep.ts +0 -1
- package/lib/extension-runtime.ts +21 -2
- package/lib/inspector.ts +56 -11
- package/lib/paths.ts +0 -7
- package/lib/prompts.ts +2 -2
- package/lib/recipes-context.ts +2 -2
- package/lib/recipes-references.ts +223 -134
- package/lib/runtime.ts +1 -5
- package/lib/tools-inspect.ts +42 -9
- package/lib/tools-local.ts +2 -0
- package/lib/tools-spawn.ts +37 -74
- package/package.json +2 -4
- package/scripts/build-dist.mjs +13 -4
- package/skills/actors/SKILL.md +6 -6
- package/skills/actors/recipes/recipe-validate.json +11 -0
- package/{dist/recipes/utility-coordinator-lock-snapshot.json → skills/actors/recipes/resource-locker-snapshot.json} +1 -3
- package/{recipes → skills/actors/recipes}/resource-locker.json +1 -3
- package/{recipes/utility-run-ops-snapshot.json → skills/actors/recipes/run-ops-snapshot.json} +1 -3
- package/skills/actors/recipes/run-summary.json +9 -0
- package/{scripts/locker.mjs → skills/actors/scripts/resource-locker.mjs} +2 -2
- package/skills/actors/scripts/run-utils.mjs +164 -0
- package/{dist → skills/actors}/scripts/validate-recipe.mjs +134 -17
- package/skills/artifacts/SKILL.md +16 -0
- package/{dist/recipes/pipeline-artifact-bundle.json → skills/artifacts/recipes/bundle.json} +4 -4
- package/{recipes/utility-artifact-write.json → skills/artifacts/recipes/file-write.json} +1 -3
- package/{recipes/utility-artifact-manifest.json → skills/artifacts/recipes/manifest.json} +1 -3
- package/{dist/recipes/pipeline-artifact-report.json → skills/artifacts/recipes/report.json} +2 -2
- package/{recipes/pipeline-artifact-write.json → skills/artifacts/recipes/write.json} +3 -3
- package/skills/artifacts/scripts/artifact-utils.mjs +63 -0
- package/skills/media/SKILL.md +16 -0
- package/{recipes/pipeline-media-library.json → skills/media/recipes/library.json} +2 -2
- package/{dist/recipes/music-player.json → skills/media/recipes/player.json} +1 -3
- package/{recipes/utility-playlist-build.json → skills/media/recipes/playlist-build.json} +1 -3
- package/skills/media/scripts/media-utils.mjs +47 -0
- package/{scripts → skills/media/scripts}/music-player.mjs +9 -3
- package/skills/project-work/SKILL.md +16 -0
- package/skills/project-work/recipes/changelog-section.json +11 -0
- package/{dist/recipes/pipeline-docs-maintenance.json → skills/project-work/recipes/docs-maintenance.json} +4 -4
- package/skills/project-work/recipes/package-summary.json +9 -0
- package/{dist/recipes/pipeline-release-readiness.json → skills/project-work/recipes/release-readiness.json} +6 -6
- package/{recipes/pipeline-release-summary.json → skills/project-work/recipes/release-summary.json} +5 -5
- package/{dist/recipes/pipeline-repo-health.json → skills/project-work/recipes/repo-health.json} +6 -6
- package/{dist/recipes/pipeline-async-run-ops.json → skills/project-work/recipes/run-ops.json} +3 -3
- package/skills/project-work/recipes/skill-summary.json +11 -0
- package/skills/project-work/scripts/project-utils.mjs +89 -0
- package/skills/recipe-memory/SKILL.md +16 -0
- package/skills/swarm/SKILL.md +4 -4
- package/{dist/recipes/pipeline-architect-coordinator.json → skills/swarm/recipes/architect.json} +5 -5
- package/{dist/recipes/pipeline-checkpoint-continuation.json → skills/swarm/recipes/checkpoint-continuation.json} +3 -3
- package/{recipes/pipeline-development-tasking.json → skills/swarm/recipes/development-tasking.json} +4 -4
- package/{recipes/lens-swarm.json → skills/swarm/recipes/lens-review.json} +1 -1
- package/{recipes/pipeline-quorum-review.json → skills/swarm/recipes/quorum-review.json} +4 -4
- package/{dist/recipes/pipeline-research-synthesis.json → skills/swarm/recipes/research-synthesis.json} +6 -6
- package/{dist/recipes/pipeline-review-readiness.json → skills/swarm/recipes/review-readiness.json} +1 -1
- package/{dist/recipes/subagents-prompts.json → skills/swarm/recipes/subagent-prompts.json} +1 -1
- package/{dist → skills/swarm}/recipes/subagent-quorum.json +1 -1
- package/{recipes → skills/swarm/recipes}/subagent-review-coordinator.json +6 -6
- package/dist/recipes/utility-changelog-section.json +0 -13
- package/dist/recipes/utility-package-summary.json +0 -11
- package/dist/recipes/utility-run-summary.json +0 -11
- package/dist/recipes/utility-skill-summary.json +0 -13
- package/dist/recipes/utility-validate-recipe.json +0 -13
- package/dist/scripts/executable-block-style.d.mts +0 -1
- package/dist/scripts/executable-block-style.mjs +0 -73
- package/dist/scripts/recipe-utils.mjs +0 -404
- package/dist/scripts/release-gates.mjs +0 -281
- package/recipes/utility-changelog-section.json +0 -13
- package/recipes/utility-package-summary.json +0 -11
- package/recipes/utility-run-summary.json +0 -11
- package/recipes/utility-skill-summary.json +0 -13
- package/recipes/utility-validate-recipe.json +0 -13
- package/scripts/executable-block-style.d.mts +0 -1
- package/scripts/executable-block-style.mjs +0 -73
- package/scripts/recipe-utils.mjs +0 -404
- package/scripts/release-gates.mjs +0 -281
- /package/dist/{recipes/utility-validation-wrapper.json → skills/actors/recipes/command-validate.json} +0 -0
- /package/dist/{recipes/utility-jsonl-tail.json → skills/actors/recipes/jsonl-tail.json} +0 -0
- /package/dist/{recipes/utility-run-state-files.json → skills/actors/recipes/run-state-files.json} +0 -0
- /package/dist/{recipes/utility-playlist-scan.json → skills/media/recipes/playlist-scan.json} +0 -0
- /package/dist/{recipes/utility-changelog-head.json → skills/project-work/recipes/changelog-head.json} +0 -0
- /package/dist/{recipes/utility-git-log.json → skills/project-work/recipes/git-log.json} +0 -0
- /package/dist/{recipes/utility-git-status.json → skills/project-work/recipes/git-status.json} +0 -0
- /package/dist/{recipes/utility-markdown-index.json → skills/project-work/recipes/markdown-index.json} +0 -0
- /package/dist/{recipes → skills/recipe-memory/recipes}/draft-review.json +0 -0
- /package/dist/{recipes → skills/recipe-memory/recipes}/tool-review.json +0 -0
- /package/dist/{recipes → skills/swarm/recipes}/subagent-artifact.json +0 -0
- /package/dist/{recipes → skills/swarm/recipes}/subagent-checkpoint.json +0 -0
- /package/dist/{recipes → skills/swarm/recipes}/subagent-conflict-report.json +0 -0
- /package/dist/{recipes → skills/swarm/recipes}/subagent-contradiction-map.json +0 -0
- /package/dist/{recipes → skills/swarm/recipes}/subagent-critic.json +0 -0
- /package/dist/{recipes → skills/swarm/recipes}/subagent-evidence-map.json +0 -0
- /package/dist/{recipes → skills/swarm/recipes}/subagent-followup.json +0 -0
- /package/dist/{recipes → skills/swarm/recipes}/subagent-judge.json +0 -0
- /package/dist/{recipes → skills/swarm/recipes}/subagent-merge.json +0 -0
- /package/dist/{recipes → skills/swarm/recipes}/subagent-normalize.json +0 -0
- /package/dist/{recipes → skills/swarm/recipes}/subagent-plan.json +0 -0
- /package/dist/{recipes → skills/swarm/recipes}/subagent-preflight.json +0 -0
- /package/dist/{recipes → skills/swarm/recipes}/subagent-prompt.json +0 -0
- /package/dist/{recipes → skills/swarm/recipes}/subagent-review.json +0 -0
- /package/dist/{recipes → skills/swarm/recipes}/subagent-task-card.json +0 -0
- /package/dist/{recipes → skills/swarm/recipes}/subagent-tools.json +0 -0
- /package/dist/{recipes → skills/swarm/recipes}/subagent-verify.json +0 -0
- /package/{recipes/utility-validation-wrapper.json → skills/actors/recipes/command-validate.json} +0 -0
- /package/{recipes/utility-jsonl-tail.json → skills/actors/recipes/jsonl-tail.json} +0 -0
- /package/{recipes/utility-run-state-files.json → skills/actors/recipes/run-state-files.json} +0 -0
- /package/{recipes/utility-playlist-scan.json → skills/media/recipes/playlist-scan.json} +0 -0
- /package/{recipes/utility-changelog-head.json → skills/project-work/recipes/changelog-head.json} +0 -0
- /package/{recipes/utility-git-log.json → skills/project-work/recipes/git-log.json} +0 -0
- /package/{recipes/utility-git-status.json → skills/project-work/recipes/git-status.json} +0 -0
- /package/{recipes/utility-markdown-index.json → skills/project-work/recipes/markdown-index.json} +0 -0
- /package/{recipes → skills/recipe-memory/recipes}/draft-review.json +0 -0
- /package/{recipes → skills/recipe-memory/recipes}/tool-review.json +0 -0
- /package/{recipes → skills/swarm/recipes}/subagent-artifact.json +0 -0
- /package/{recipes → skills/swarm/recipes}/subagent-checkpoint.json +0 -0
- /package/{recipes → skills/swarm/recipes}/subagent-conflict-report.json +0 -0
- /package/{recipes → skills/swarm/recipes}/subagent-contradiction-map.json +0 -0
- /package/{recipes → skills/swarm/recipes}/subagent-critic.json +0 -0
- /package/{recipes → skills/swarm/recipes}/subagent-evidence-map.json +0 -0
- /package/{recipes → skills/swarm/recipes}/subagent-followup.json +0 -0
- /package/{recipes → skills/swarm/recipes}/subagent-judge.json +0 -0
- /package/{recipes → skills/swarm/recipes}/subagent-merge.json +0 -0
- /package/{recipes → skills/swarm/recipes}/subagent-normalize.json +0 -0
- /package/{recipes → skills/swarm/recipes}/subagent-plan.json +0 -0
- /package/{recipes → skills/swarm/recipes}/subagent-preflight.json +0 -0
- /package/{recipes → skills/swarm/recipes}/subagent-prompt.json +0 -0
- /package/{recipes → skills/swarm/recipes}/subagent-review.json +0 -0
- /package/{recipes → skills/swarm/recipes}/subagent-task-card.json +0 -0
- /package/{recipes → skills/swarm/recipes}/subagent-tools.json +0 -0
- /package/{recipes → skills/swarm/recipes}/subagent-verify.json +0 -0
package/dist/lib/tools-spawn.js
CHANGED
|
@@ -9,7 +9,6 @@ import * as AsyncRuns from "./async-runs.js";
|
|
|
9
9
|
import { withFileMutationLock } from "./file-state.js";
|
|
10
10
|
import * as ModelContext from "./model-context.js";
|
|
11
11
|
import * as Paths from "./paths.js";
|
|
12
|
-
import * as RecipesDiscovery from "./recipes-discovery.js";
|
|
13
12
|
import * as RecipesUsage from "./recipes-usage.js";
|
|
14
13
|
import * as Schema from "./schema.js";
|
|
15
14
|
import * as ToolsResponse from "./tools-response.js";
|
|
@@ -18,17 +17,6 @@ const maybeJsonText = ToolsResponse.maybeJsonText;
|
|
|
18
17
|
function getRunOwnerId(ctx) {
|
|
19
18
|
return ctx.sessionManager?.getSessionId?.();
|
|
20
19
|
}
|
|
21
|
-
function shadowedRecipeLaunchDiagnostic(recipe) {
|
|
22
|
-
if (typeof recipe !== "string" ||
|
|
23
|
-
recipe.includes("/") ||
|
|
24
|
-
recipe.includes("~"))
|
|
25
|
-
return undefined;
|
|
26
|
-
const discovery = RecipesDiscovery.discoverRecipeSources([
|
|
27
|
-
{ root: Paths.getRecipeRoot(), defaultTool: true, mutableUsage: true },
|
|
28
|
-
{ root: Paths.getPackagedRecipeRoot(), defaultTool: false },
|
|
29
|
-
]);
|
|
30
|
-
return RecipesDiscovery.getShadowedLaunchDiagnostic(discovery, recipe);
|
|
31
|
-
}
|
|
32
20
|
function draftRecipeName(run) {
|
|
33
21
|
return `${run.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "spawn"}.json`;
|
|
34
22
|
}
|
|
@@ -60,16 +48,6 @@ function writeSpawnDraftRecipe(input, meta) {
|
|
|
60
48
|
RecipesUsage.recordRecipeLaunch(path, new Date(), "spawn", Paths.getRecipeRoot());
|
|
61
49
|
return path;
|
|
62
50
|
}
|
|
63
|
-
function enhanceSpawnRecipeError(error, recipe) {
|
|
64
|
-
const diagnostic = shadowedRecipeLaunchDiagnostic(recipe);
|
|
65
|
-
if (!diagnostic)
|
|
66
|
-
return error instanceof Error ? error : new Error(String(error));
|
|
67
|
-
const original = error instanceof Error ? error.message : String(error);
|
|
68
|
-
return Object.assign(new Error(`${original} reason=${diagnostic.reason} active_path=${diagnostic.active_path} blocked_fallback=${diagnostic.blocked_fallback} hint=${diagnostic.hint}`), {
|
|
69
|
-
...diagnostic,
|
|
70
|
-
original_error: original,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
51
|
function runIdFromTarget(target) {
|
|
74
52
|
if (!target)
|
|
75
53
|
return undefined;
|
|
@@ -90,8 +68,8 @@ export function createSpawnToolDefinition() {
|
|
|
90
68
|
parameters: Schema.objectSchema({
|
|
91
69
|
artifacts: Schema.looseObjectSchema("Optional named artifact paths for the spawned Run."),
|
|
92
70
|
as: Schema.stringSchema("Optional Run identity in exact run:<id> form."),
|
|
93
|
-
file: Schema.stringSchema("Optional
|
|
94
|
-
recipe: Schema.stringSchema("Alias for file
|
|
71
|
+
file: Schema.stringSchema("Optional Recipe reference as <skill>/<recipe> or an explicit .json/.md file path."),
|
|
72
|
+
recipe: Schema.stringSchema("Alias for file."),
|
|
95
73
|
template: Schema.unionSchema([
|
|
96
74
|
Schema.stringSchema("Inline command template string"),
|
|
97
75
|
Schema.arraySchema("Inline command-template sequence or parallel tree"),
|
|
@@ -112,34 +90,28 @@ export function createSpawnToolDefinition() {
|
|
|
112
90
|
: typeof input.recipe === "string"
|
|
113
91
|
? input.recipe
|
|
114
92
|
: undefined;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
: {}),
|
|
138
|
-
}, ctx.cwd);
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
141
|
-
throw enhanceSpawnRecipeError(error, recipe);
|
|
142
|
-
}
|
|
93
|
+
const meta = AsyncRuns.startRun({
|
|
94
|
+
file: recipe,
|
|
95
|
+
launch_source: "spawn",
|
|
96
|
+
launch_correlation: { tool_call_id: toolCallId },
|
|
97
|
+
ownerId: getRunOwnerId(ctx),
|
|
98
|
+
...(input.transport_context
|
|
99
|
+
? { transport_context: asRecord(input.transport_context) } : {}),
|
|
100
|
+
run_id: runId,
|
|
101
|
+
...(input.template !== undefined
|
|
102
|
+
? {
|
|
103
|
+
template: input.template,
|
|
104
|
+
}
|
|
105
|
+
: {}),
|
|
106
|
+
values: ModelContext.withCurrentModelValues(asRecord(input.values), ctx),
|
|
107
|
+
...(input.artifacts &&
|
|
108
|
+
typeof input.artifacts === "object" &&
|
|
109
|
+
!Array.isArray(input.artifacts)
|
|
110
|
+
? {
|
|
111
|
+
artifacts: input.artifacts,
|
|
112
|
+
}
|
|
113
|
+
: {}),
|
|
114
|
+
}, ctx.cwd, { skillContext: ctx.activeSkillRecipeContext });
|
|
143
115
|
const draftRecipe = writeSpawnDraftRecipe(input, meta);
|
|
144
116
|
const nextActions = ToolsResponse.runNextActions(meta.run);
|
|
145
117
|
const details = {
|
|
@@ -40,12 +40,21 @@ writeFileSync(
|
|
|
40
40
|
"utf8",
|
|
41
41
|
);
|
|
42
42
|
|
|
43
|
-
for (const dir of ["scripts", "
|
|
43
|
+
for (const dir of ["scripts", "fixtures", "skills"]) {
|
|
44
44
|
cpSync(dir, join("dist", dir), { recursive: true });
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
.
|
|
49
|
-
|
|
47
|
+
function listModuleScripts(root) {
|
|
48
|
+
return readdirSync(root, { withFileTypes: true }).flatMap((entry) => {
|
|
49
|
+
const path = join(root, entry.name);
|
|
50
|
+
if (entry.isDirectory()) return listModuleScripts(path);
|
|
51
|
+
return entry.isFile() && entry.name.endsWith(".mjs") ? [path] : [];
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const builtScripts = [
|
|
56
|
+
...listModuleScripts(join("dist", "scripts")),
|
|
57
|
+
...listModuleScripts(join("dist", "skills")),
|
|
58
|
+
];
|
|
50
59
|
|
|
51
60
|
run(process.execPath, ["--check", ...builtScripts]);
|
|
@@ -29,7 +29,7 @@ A Recipe defines execution. It may declare named typed args, inline fallbacks, c
|
|
|
29
29
|
|
|
30
30
|
Do not declare Control for ordinary one-shot work. Runtime lifecycle actions such as `kill` stay runtime-owned and must not appear in Recipe Control declarations. Imported Recipes act as local definitions inside one Run; they do not create nested Runs unless execution explicitly spawns them.
|
|
31
31
|
|
|
32
|
-
Prefer maintained
|
|
32
|
+
Prefer maintained Skill-owned Recipes over ad hoc wrappers. Skill Recipe identity is `<active Skill name>/<Recipe filename stem>`; Recipe files have no top-level `name`. `SKILL.md` `name` is Pi host metadata matching its directory, not an additional pi-actors identity field. Use `<skill>/<recipe>` for an exact direct component or an explicit `.json` / `.md` path. Entry paths resolve from invocation `cwd`; relative imports resolve from the importing Recipe directory. File-backed Recipes own `{recipe_dir}` and Skill Recipes own `{skill_dir}`; callers never pass or override these origins. Skill Recipes are components, not automatic tools. Keep model, thinking, mission, concurrency, quorum, and timeout choices caller-owned unless a Recipe documents a stable policy.
|
|
33
33
|
|
|
34
34
|
## Trace
|
|
35
35
|
|
|
@@ -90,11 +90,11 @@ If work may outlive the current turn, needs steering, produces artifacts, fans o
|
|
|
90
90
|
|
|
91
91
|
## Top Recipes
|
|
92
92
|
|
|
93
|
-
- [Repository health](
|
|
94
|
-
- [Quorum review](
|
|
95
|
-
- [Artifact bundle](
|
|
96
|
-
- [Music player service](
|
|
97
|
-
- [Resource locker service](
|
|
93
|
+
- [Repository health](../project-work/recipes/repo-health.json)
|
|
94
|
+
- [Quorum review](../swarm/recipes/quorum-review.json)
|
|
95
|
+
- [Artifact bundle](../artifacts/recipes/bundle.json)
|
|
96
|
+
- [Music player service](../media/recipes/player.json)
|
|
97
|
+
- [Resource locker service](./recipes/resource-locker.json)
|
|
98
98
|
|
|
99
99
|
## Deep References
|
|
100
100
|
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"args": [
|
|
3
|
-
"repo:path",
|
|
4
3
|
"state_dir:path",
|
|
5
4
|
"lines:int"
|
|
6
5
|
],
|
|
7
6
|
"defaults": {
|
|
8
|
-
"repo": "~/.pi/agent/extensions/pi-actors",
|
|
9
7
|
"state_dir": "~/.pi/agent/tmp/pi-actors/runs/coordinator-locker",
|
|
10
8
|
"lines": "20"
|
|
11
9
|
},
|
|
12
|
-
"template": "{
|
|
10
|
+
"template": "{skill_dir}/scripts/resource-locker.mjs snapshot --state-dir {state_dir} --lines {lines}"
|
|
13
11
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"async": true,
|
|
3
3
|
"args": [
|
|
4
|
-
"repo:path",
|
|
5
4
|
"lease_ms:int"
|
|
6
5
|
],
|
|
7
6
|
"defaults": {
|
|
8
|
-
"repo": "~/.pi/agent/extensions/pi-actors",
|
|
9
7
|
"lease_ms": "600000"
|
|
10
8
|
},
|
|
11
9
|
"control": [
|
|
@@ -23,5 +21,5 @@
|
|
|
23
21
|
"locks": "{state_dir}/locks.json",
|
|
24
22
|
"journal": "{state_dir}/journal.jsonl"
|
|
25
23
|
},
|
|
26
|
-
"template": "{
|
|
24
|
+
"template": "{skill_dir}/scripts/resource-locker.mjs serve --state-dir {state_dir} --lease-ms {lease_ms}"
|
|
27
25
|
}
|
package/dist/{recipes/utility-run-ops-snapshot.json → skills/actors/recipes/run-ops-snapshot.json}
RENAMED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"args": [
|
|
3
|
-
"repo:path",
|
|
4
3
|
"state_root:path",
|
|
5
4
|
"run_id:string",
|
|
6
5
|
"lines:int",
|
|
7
6
|
"stale_minutes:int"
|
|
8
7
|
],
|
|
9
8
|
"defaults": {
|
|
10
|
-
"repo": "~/.pi/agent/extensions/pi-actors",
|
|
11
9
|
"state_root": "~/.pi/agent/tmp/pi-actors/runs",
|
|
12
10
|
"run_id": "music",
|
|
13
11
|
"lines": "80",
|
|
14
12
|
"stale_minutes": "60"
|
|
15
13
|
},
|
|
16
|
-
"template": "{
|
|
14
|
+
"template": "{skill_dir}/scripts/run-utils.mjs run-ops-snapshot {state_root} {run_id} {lines} {stale_minutes}"
|
|
17
15
|
}
|
|
@@ -21,12 +21,12 @@ import readline from "node:readline";
|
|
|
21
21
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
22
22
|
|
|
23
23
|
function packageRoot() {
|
|
24
|
-
return dirname(dirname(fileURLToPath(import.meta.url)));
|
|
24
|
+
return dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url)))));
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
async function importRuntimeModule(name) {
|
|
28
28
|
const root = packageRoot();
|
|
29
|
-
const compiled = join(root, "
|
|
29
|
+
const compiled = join(root, "lib", `${name}.js`);
|
|
30
30
|
const source = join(root, "lib", `${name}.ts`);
|
|
31
31
|
return await import(pathToFileURL(existsSync(compiled) ? compiled : source).href);
|
|
32
32
|
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/** Actor-owned Run summary utilities. */
|
|
4
|
+
|
|
5
|
+
// @ts-nocheck
|
|
6
|
+
|
|
7
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
8
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
9
|
+
|
|
10
|
+
function fail(message) {
|
|
11
|
+
console.error(message);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function usage() {
|
|
16
|
+
console.error(`Usage:
|
|
17
|
+
run-utils.mjs run-summary <state-root>
|
|
18
|
+
run-utils.mjs run-ops-snapshot <state-root> <run-id> [lines] [stale-minutes]`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function walkFiles(dir, maxDepth = 2, depth = 0, out = []) {
|
|
22
|
+
if (depth > maxDepth || !existsSync(dir)) return out;
|
|
23
|
+
for (const entry of readdirSync(dir)) {
|
|
24
|
+
const path = join(dir, entry);
|
|
25
|
+
const stat = statSync(path);
|
|
26
|
+
if (stat.isDirectory()) walkFiles(path, maxDepth, depth + 1, out);
|
|
27
|
+
else if (stat.isFile()) out.push(path);
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function readJson(file) {
|
|
33
|
+
if (!existsSync(file)) return undefined;
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
36
|
+
} catch {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getRunStatus(run, progress, result) {
|
|
42
|
+
if (progress?.phase) return progress.phase;
|
|
43
|
+
if (result?.code !== undefined) return result.code === 0 ? "done" : "failed";
|
|
44
|
+
return run.status ?? "unknown";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function collectRunSummary(rootValue) {
|
|
48
|
+
const root = resolve(rootValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"));
|
|
49
|
+
const files = walkFiles(root, 2).filter((file) => basename(file) === "run.json");
|
|
50
|
+
const rows = [];
|
|
51
|
+
for (const file of files) {
|
|
52
|
+
const run = readJson(file);
|
|
53
|
+
if (!run) {
|
|
54
|
+
rows.push({ run: relative(root, file), status: "invalid-json", recipe: "", updated: "" });
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const runDir = dirname(file);
|
|
58
|
+
const progress = readJson(join(runDir, "progress.json"));
|
|
59
|
+
const result = readJson(join(runDir, "result.json"));
|
|
60
|
+
rows.push({
|
|
61
|
+
run: run.run_id ?? run.run ?? relative(root, file).split(sep)[0],
|
|
62
|
+
status: getRunStatus(run, progress, result),
|
|
63
|
+
recipe: run.recipe ?? run.recipe_file ?? "",
|
|
64
|
+
updated:
|
|
65
|
+
progress?.updatedAt ??
|
|
66
|
+
result?.completedAt ??
|
|
67
|
+
run.updated_at ??
|
|
68
|
+
run.completed_at ??
|
|
69
|
+
run.started_at ??
|
|
70
|
+
"",
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
rows.sort((a, b) => `${a.status}:${a.run}`.localeCompare(`${b.status}:${b.run}`));
|
|
74
|
+
return rows;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function tailJsonl(fileValue, linesValue = "80") {
|
|
78
|
+
const file = resolve(fileValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"));
|
|
79
|
+
if (!existsSync(file)) return [];
|
|
80
|
+
const lines = Number.parseInt(linesValue, 10);
|
|
81
|
+
const count = Number.isFinite(lines) && lines > 0 ? lines : 80;
|
|
82
|
+
return readFileSync(file, "utf8")
|
|
83
|
+
.trimEnd()
|
|
84
|
+
.split("\n")
|
|
85
|
+
.filter(Boolean)
|
|
86
|
+
.slice(-count)
|
|
87
|
+
.map((line) => {
|
|
88
|
+
try {
|
|
89
|
+
return JSON.parse(line);
|
|
90
|
+
} catch {
|
|
91
|
+
return { raw: line };
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function runOpsSnapshot(
|
|
97
|
+
rootValue,
|
|
98
|
+
runIdValue = "music",
|
|
99
|
+
linesValue = "80",
|
|
100
|
+
staleMinutesValue = "60",
|
|
101
|
+
) {
|
|
102
|
+
const runs = collectRunSummary(rootValue);
|
|
103
|
+
const root = resolve(rootValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"));
|
|
104
|
+
const inspectedRun = String(runIdValue || "music");
|
|
105
|
+
const staleMs = Number(staleMinutesValue) * 60 * 1000;
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
const recommendations = runs.flatMap((run) => {
|
|
108
|
+
const updatedMs = Date.parse(run.updated || "");
|
|
109
|
+
const stale =
|
|
110
|
+
Number.isFinite(updatedMs) &&
|
|
111
|
+
Number.isFinite(staleMs) &&
|
|
112
|
+
now - updatedMs > staleMs;
|
|
113
|
+
if (run.status === "running" && stale) {
|
|
114
|
+
return [{
|
|
115
|
+
run: run.run,
|
|
116
|
+
reason: "running-stale",
|
|
117
|
+
suggested_inspect: { target: `run:${run.run}`, view: "control" },
|
|
118
|
+
}];
|
|
119
|
+
}
|
|
120
|
+
if (["failed", "exited", "killed"].includes(run.status)) {
|
|
121
|
+
return [{
|
|
122
|
+
run: run.run,
|
|
123
|
+
reason: `terminal-${run.status}`,
|
|
124
|
+
suggested_inspect: { target: `run:${run.run}`, view: "trace" },
|
|
125
|
+
}];
|
|
126
|
+
}
|
|
127
|
+
return [];
|
|
128
|
+
});
|
|
129
|
+
console.log(JSON.stringify({
|
|
130
|
+
runs,
|
|
131
|
+
inspectedRun,
|
|
132
|
+
trace: tailJsonl(join(root, inspectedRun, "trace.jsonl"), linesValue),
|
|
133
|
+
recommendations,
|
|
134
|
+
}, null, 2));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function runSummary(rootValue) {
|
|
138
|
+
console.log(JSON.stringify(collectRunSummary(rootValue), null, 2));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function run(argv = process.argv.slice(2)) {
|
|
142
|
+
const [command, ...args] = argv;
|
|
143
|
+
if (command === "run-summary") {
|
|
144
|
+
runSummary(args[0] ?? "~/.pi/agent/tmp/pi-actors/runs");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (command === "run-ops-snapshot") {
|
|
148
|
+
runOpsSnapshot(
|
|
149
|
+
args[0] ?? "~/.pi/agent/tmp/pi-actors/runs",
|
|
150
|
+
args[1] ?? "music",
|
|
151
|
+
args[2],
|
|
152
|
+
args[3],
|
|
153
|
+
);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
usage();
|
|
157
|
+
fail(`Unknown command: ${command ?? ""}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
run();
|
|
162
|
+
} catch (error) {
|
|
163
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
164
|
+
}
|
|
@@ -9,27 +9,49 @@
|
|
|
9
9
|
|
|
10
10
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
11
11
|
import { homedir } from "node:os";
|
|
12
|
-
import { dirname, join, resolve } from "node:path";
|
|
12
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
13
13
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
14
14
|
|
|
15
15
|
function packageRoot() {
|
|
16
|
-
return dirname(dirname(fileURLToPath(import.meta.url)));
|
|
16
|
+
return dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url)))));
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
async function importRuntimeModule(name) {
|
|
20
20
|
const root = packageRoot();
|
|
21
|
-
const
|
|
21
|
+
const localCompiled = join(root, "lib", `${name}.js`);
|
|
22
|
+
const installedCompiled = join(root, "dist", "lib", `${name}.js`);
|
|
22
23
|
const source = join(root, "lib", `${name}.ts`);
|
|
23
|
-
|
|
24
|
+
const module = existsSync(localCompiled)
|
|
25
|
+
? localCompiled
|
|
26
|
+
: root.includes(`${sep}node_modules${sep}`) && existsSync(installedCompiled)
|
|
27
|
+
? installedCompiled
|
|
28
|
+
: source;
|
|
29
|
+
return await import(pathToFileURL(module).href);
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
const {
|
|
32
|
+
const {
|
|
33
|
+
createActiveSkillRecipeContext,
|
|
34
|
+
readRawRecipeConfig,
|
|
35
|
+
readResolvedRecipeConfig,
|
|
36
|
+
} = await importRuntimeModule("recipes-references");
|
|
37
|
+
|
|
38
|
+
const skillsDir = join(packageRoot(), "skills");
|
|
39
|
+
const packageSkillContext = createActiveSkillRecipeContext(
|
|
40
|
+
existsSync(skillsDir)
|
|
41
|
+
? readdirSync(skillsDir, { withFileTypes: true })
|
|
42
|
+
.filter((entry) => entry.isDirectory())
|
|
43
|
+
.map((entry) => ({
|
|
44
|
+
name: entry.name,
|
|
45
|
+
baseDir: join(skillsDir, entry.name),
|
|
46
|
+
}))
|
|
47
|
+
: [],
|
|
48
|
+
);
|
|
27
49
|
|
|
28
50
|
export function validateRecipeUsage() {
|
|
29
51
|
return `Usage:
|
|
30
|
-
validate-recipe.mjs <recipe-file-or-dir> [--all] [--qa] [--summary]
|
|
52
|
+
validate-recipe.mjs <recipe-file-or-dir> [--all] [--skills] [--qa] [--summary]
|
|
31
53
|
|
|
32
|
-
Validates one template
|
|
54
|
+
Validates one template Recipe file or all direct *.json/*.md files in a directory with --all. Use --skills on a Skills root to validate every direct <skill>/recipes component and reject nested files or duplicate stems. Add --qa for shipped-component capability checks, where diagnostics and warnings fail validation. Add --summary for compact CLI output.`;
|
|
33
55
|
}
|
|
34
56
|
|
|
35
57
|
function expandPath(value) {
|
|
@@ -63,6 +85,79 @@ function recipeFiles(target, all) {
|
|
|
63
85
|
.map((file) => resolve(target, file));
|
|
64
86
|
}
|
|
65
87
|
|
|
88
|
+
function nestedRecipeFiles(root) {
|
|
89
|
+
return readdirSync(root, { withFileTypes: true }).flatMap((entry) => {
|
|
90
|
+
const path = join(root, entry.name);
|
|
91
|
+
if (entry.isDirectory()) return nestedRecipeFiles(path);
|
|
92
|
+
return entry.isFile() && /\.(?:json|md)$/.test(entry.name) ? [path] : [];
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function scanSkillRecipes(target) {
|
|
97
|
+
if (!existsSync(target) || !statSync(target).isDirectory())
|
|
98
|
+
throw new Error(`Skills root is not a directory: ${target}`);
|
|
99
|
+
const skills = readdirSync(target, { withFileTypes: true })
|
|
100
|
+
.filter((entry) => entry.isDirectory())
|
|
101
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
102
|
+
const files = [];
|
|
103
|
+
const failures = [];
|
|
104
|
+
for (const skill of skills) {
|
|
105
|
+
const recipeDir = join(target, skill.name, "recipes");
|
|
106
|
+
if (!existsSync(recipeDir)) continue;
|
|
107
|
+
const direct = readdirSync(recipeDir, { withFileTypes: true });
|
|
108
|
+
const byStem = new Map();
|
|
109
|
+
for (const entry of direct) {
|
|
110
|
+
const path = join(recipeDir, entry.name);
|
|
111
|
+
if (entry.isDirectory()) {
|
|
112
|
+
for (const nested of nestedRecipeFiles(path)) {
|
|
113
|
+
failures.push({
|
|
114
|
+
file: nested,
|
|
115
|
+
ok: false,
|
|
116
|
+
error: `Nested Skill Recipe files are not allowed: ${relative(recipeDir, nested)}`,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (!entry.isFile() || !/\.(?:json|md)$/.test(entry.name)) continue;
|
|
122
|
+
files.push(path);
|
|
123
|
+
const stem = basename(entry.name, extname(entry.name));
|
|
124
|
+
const previous = byStem.get(stem);
|
|
125
|
+
if (previous) {
|
|
126
|
+
failures.push({
|
|
127
|
+
file: path,
|
|
128
|
+
ok: false,
|
|
129
|
+
error: `Skill Recipe stem collision: ${skill.name}/${stem} has both ${extname(previous)} and ${extname(path)} files`,
|
|
130
|
+
});
|
|
131
|
+
} else byStem.set(stem, path);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
let skillContext = packageSkillContext;
|
|
135
|
+
try {
|
|
136
|
+
skillContext = createActiveSkillRecipeContext(
|
|
137
|
+
skills.map((entry) => ({ name: entry.name, baseDir: join(target, entry.name) })),
|
|
138
|
+
);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
failures.push({
|
|
141
|
+
file: target,
|
|
142
|
+
ok: false,
|
|
143
|
+
error: error instanceof Error ? error.message : String(error),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
files: files.sort((a, b) => a.localeCompare(b)),
|
|
148
|
+
failures,
|
|
149
|
+
skillContext,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function collectStrings(value, out = []) {
|
|
154
|
+
if (typeof value === "string") out.push(value);
|
|
155
|
+
else if (Array.isArray(value)) value.forEach((item) => collectStrings(item, out));
|
|
156
|
+
else if (value && typeof value === "object")
|
|
157
|
+
Object.values(value).forEach((item) => collectStrings(item, out));
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
|
|
66
161
|
function collectTemplateStrings(value, out = []) {
|
|
67
162
|
if (typeof value === "string") out.push(value);
|
|
68
163
|
else if (Array.isArray(value)) value.forEach((item) => collectTemplateStrings(item, out));
|
|
@@ -105,24 +200,35 @@ function validateArtifactDeclarations(config) {
|
|
|
105
200
|
|
|
106
201
|
function validateHelperPaths(file, config) {
|
|
107
202
|
const diagnostics = [];
|
|
203
|
+
const skillDir = dirname(dirname(file));
|
|
108
204
|
for (const template of collectTemplateStrings(config.template)) {
|
|
109
205
|
if (/(^|\s)(?:node\s+)?scripts\/[\w.-]+\.mjs/.test(template))
|
|
110
|
-
diagnostics.push("template: helper scripts must be referenced through {
|
|
111
|
-
for (const match of template.matchAll(/\{
|
|
112
|
-
const
|
|
113
|
-
if (!existsSync(
|
|
114
|
-
diagnostics.push(`template: referenced helper
|
|
206
|
+
diagnostics.push("template: Skill helper scripts must be referenced through {skill_dir}/scripts");
|
|
207
|
+
for (const match of template.matchAll(/\{skill_dir\}\/([^\s"']+)/g)) {
|
|
208
|
+
const target = match[1].replace(/[),;]+$/, "");
|
|
209
|
+
if (!existsSync(join(skillDir, target)))
|
|
210
|
+
diagnostics.push(`template: referenced Skill helper not found: ${target}`);
|
|
115
211
|
}
|
|
116
212
|
}
|
|
117
213
|
return diagnostics;
|
|
118
214
|
}
|
|
119
215
|
|
|
216
|
+
function validatePortablePaths(config) {
|
|
217
|
+
const diagnostics = [];
|
|
218
|
+
for (const value of collectStrings(config)) {
|
|
219
|
+
if (/^(?:\/home\/|\/Users\/|[A-Za-z]:[\\/])/.test(value))
|
|
220
|
+
diagnostics.push(`recipe: must not use a machine-local absolute path: ${value}`);
|
|
221
|
+
}
|
|
222
|
+
return diagnostics;
|
|
223
|
+
}
|
|
224
|
+
|
|
120
225
|
function qaDiagnostics(file, config) {
|
|
121
226
|
const diagnostics = [];
|
|
122
227
|
const warnings = [];
|
|
123
228
|
if (config.mailbox !== undefined)
|
|
124
229
|
diagnostics.push("recipe.mailbox was removed; use control actions and Trace events");
|
|
125
230
|
diagnostics.push(...validateArtifactDeclarations(config));
|
|
231
|
+
diagnostics.push(...validatePortablePaths(config));
|
|
126
232
|
diagnostics.push(...validateHelperPaths(file, config));
|
|
127
233
|
const platformSpecificTemplate = collectTemplateStrings(config.template).some(
|
|
128
234
|
(template) =>
|
|
@@ -139,12 +245,16 @@ function qaOk(qaReport) {
|
|
|
139
245
|
return qaReport.diagnostics.length === 0 && qaReport.warnings.length === 0;
|
|
140
246
|
}
|
|
141
247
|
|
|
142
|
-
function validateFile(file, qa = false) {
|
|
248
|
+
function validateFile(file, qa = false, skillContext = packageSkillContext) {
|
|
143
249
|
try {
|
|
144
|
-
const
|
|
250
|
+
const rawConfig = readRawRecipeConfig(file);
|
|
251
|
+
const config = readResolvedRecipeConfig(file, [], { skillContext });
|
|
145
252
|
if (!config?.template)
|
|
146
253
|
throw new Error("Recipe must define a non-empty template.");
|
|
147
|
-
const
|
|
254
|
+
const expectedName = basename(file, extname(file));
|
|
255
|
+
if (config.name !== expectedName)
|
|
256
|
+
throw new Error(`Recipe identity mismatch: expected ${expectedName}, received ${config.name ?? "<none>"}`);
|
|
257
|
+
const qaReport = qa ? qaDiagnostics(file, rawConfig) : { diagnostics: [], warnings: [] };
|
|
148
258
|
return {
|
|
149
259
|
file,
|
|
150
260
|
ok: qaOk(qaReport),
|
|
@@ -183,12 +293,19 @@ function validateFile(file, qa = false) {
|
|
|
183
293
|
export function validateRecipes(argv) {
|
|
184
294
|
const targetArg = argv.find((arg) => !arg.startsWith("-"));
|
|
185
295
|
const all = argv.includes("--all");
|
|
296
|
+
const skills = argv.includes("--skills");
|
|
186
297
|
const qa = argv.includes("--qa");
|
|
187
298
|
if (!targetArg || argv.includes("--help") || argv.includes("-h")) {
|
|
188
299
|
return { help: true, ok: Boolean(targetArg), usage: validateRecipeUsage() };
|
|
189
300
|
}
|
|
190
|
-
const
|
|
191
|
-
const
|
|
301
|
+
const target = expandPath(targetArg);
|
|
302
|
+
const scan = skills
|
|
303
|
+
? scanSkillRecipes(target)
|
|
304
|
+
: { files: recipeFiles(target, all), failures: [], skillContext: packageSkillContext };
|
|
305
|
+
const results = [
|
|
306
|
+
...scan.failures,
|
|
307
|
+
...scan.files.map((file) => validateFile(file, qa, scan.skillContext)),
|
|
308
|
+
];
|
|
192
309
|
const failed = results.filter((result) => !result.ok).length;
|
|
193
310
|
return {
|
|
194
311
|
ok: failed === 0,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: artifacts
|
|
3
|
+
description: Deterministic artifact writing, reporting, manifests, and bundles for reusable actor workflows.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Artifacts
|
|
7
|
+
|
|
8
|
+
Own reusable capabilities that turn bounded input or prior command output into declared files, reports, manifests, and artifact bundles.
|
|
9
|
+
|
|
10
|
+
## Scope
|
|
11
|
+
|
|
12
|
+
- Write or assemble caller-declared artifacts with explicit paths and overwrite policy.
|
|
13
|
+
- Produce bounded human-readable reports and machine-readable manifests.
|
|
14
|
+
- Compose validation evidence into artifact outputs without owning the validation policy.
|
|
15
|
+
|
|
16
|
+
This Skill does not own repository workflow, multi-agent review methodology, Run lifecycle, retention policy, or transport delivery. Its Recipe identity is `artifacts/<filename stem>`; Recipe files have no top-level `name`. Cross-capability composition uses exact `<skill>/<recipe>` references; helper-backed Recipes use only this Skill's `scripts/` through `{skill_dir}`.
|