@yycholla/pi-dynamic-workflows 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +325 -0
- package/assets/readme/hero.png +0 -0
- package/assets/readme/hero.svg +70 -0
- package/assets/readme/package-cover.png +0 -0
- package/assets/readme/package-cover.svg +66 -0
- package/assets/readme/workflow.png +0 -0
- package/assets/readme/workflow.svg +77 -0
- package/dist/accept-workflow-guidance.d.ts +14 -0
- package/dist/accept-workflow-guidance.js +53 -0
- package/dist/adversarial-review.d.ts +30 -0
- package/dist/adversarial-review.js +107 -0
- package/dist/agent-history.d.ts +20 -0
- package/dist/agent-history.js +122 -0
- package/dist/agent-registry.d.ts +83 -0
- package/dist/agent-registry.js +190 -0
- package/dist/agent.d.ts +364 -0
- package/dist/agent.js +714 -0
- package/dist/builtin-commands.d.ts +19 -0
- package/dist/builtin-commands.js +251 -0
- package/dist/builtin-workflows.d.ts +45 -0
- package/dist/builtin-workflows.js +121 -0
- package/dist/code-review.d.ts +26 -0
- package/dist/code-review.js +181 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.js +44 -0
- package/dist/deep-research.d.ts +30 -0
- package/dist/deep-research.js +124 -0
- package/dist/display.d.ts +134 -0
- package/dist/display.js +248 -0
- package/dist/effort-command.d.ts +28 -0
- package/dist/effort-command.js +68 -0
- package/dist/enums.d.ts +69 -0
- package/dist/enums.js +78 -0
- package/dist/errors.d.ts +113 -0
- package/dist/errors.js +140 -0
- package/dist/extension-reload.d.ts +37 -0
- package/dist/extension-reload.js +78 -0
- package/dist/fs-persistence.d.ts +63 -0
- package/dist/fs-persistence.js +102 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.js +35 -0
- package/dist/logger.d.ts +21 -0
- package/dist/logger.js +66 -0
- package/dist/model-routing.d.ts +30 -0
- package/dist/model-routing.js +50 -0
- package/dist/model-spec.d.ts +29 -0
- package/dist/model-spec.js +252 -0
- package/dist/model-tier-config.d.ts +133 -0
- package/dist/model-tier-config.js +249 -0
- package/dist/run-persistence.d.ts +180 -0
- package/dist/run-persistence.js +294 -0
- package/dist/saved-commands.d.ts +28 -0
- package/dist/saved-commands.js +100 -0
- package/dist/shared-store.d.ts +98 -0
- package/dist/shared-store.js +212 -0
- package/dist/structured-output.d.ts +19 -0
- package/dist/structured-output.js +30 -0
- package/dist/task-panel.d.ts +61 -0
- package/dist/task-panel.js +422 -0
- package/dist/usage-limit-scheduler.d.ts +145 -0
- package/dist/usage-limit-scheduler.js +368 -0
- package/dist/web-tools.d.ts +20 -0
- package/dist/web-tools.js +120 -0
- package/dist/workflow-authoring-coverage.d.ts +70 -0
- package/dist/workflow-authoring-coverage.js +421 -0
- package/dist/workflow-authoring-reference.d.ts +20 -0
- package/dist/workflow-authoring-reference.js +156 -0
- package/dist/workflow-capability-contract.d.ts +131 -0
- package/dist/workflow-capability-contract.js +604 -0
- package/dist/workflow-commands.d.ts +18 -0
- package/dist/workflow-commands.js +260 -0
- package/dist/workflow-comprehension.d.ts +133 -0
- package/dist/workflow-comprehension.js +1321 -0
- package/dist/workflow-context-measurement.d.ts +72 -0
- package/dist/workflow-context-measurement.js +213 -0
- package/dist/workflow-control-tool.d.ts +30 -0
- package/dist/workflow-control-tool.js +176 -0
- package/dist/workflow-delivery-choice.d.ts +20 -0
- package/dist/workflow-delivery-choice.js +48 -0
- package/dist/workflow-editor.d.ts +93 -0
- package/dist/workflow-editor.js +363 -0
- package/dist/workflow-manager.d.ts +492 -0
- package/dist/workflow-manager.js +1124 -0
- package/dist/workflow-paths.d.ts +22 -0
- package/dist/workflow-paths.js +46 -0
- package/dist/workflow-release-gate.d.ts +39 -0
- package/dist/workflow-release-gate.js +309 -0
- package/dist/workflow-saved.d.ts +38 -0
- package/dist/workflow-saved.js +126 -0
- package/dist/workflow-settings.d.ts +70 -0
- package/dist/workflow-settings.js +131 -0
- package/dist/workflow-tool.d.ts +71 -0
- package/dist/workflow-tool.js +367 -0
- package/dist/workflow-ui.d.ts +182 -0
- package/dist/workflow-ui.js +1587 -0
- package/dist/workflow.d.ts +333 -0
- package/dist/workflow.js +1151 -0
- package/dist/workflows-models-command.d.ts +31 -0
- package/dist/workflows-models-command.js +156 -0
- package/dist/worktree.d.ts +25 -0
- package/dist/worktree.js +61 -0
- package/extensions/workflow.ts +151 -0
- package/package.json +104 -0
- package/skills/workflow-authoring/SKILL.md +30 -0
- package/skills/workflow-authoring/examples/adversarial-verification.js +63 -0
- package/skills/workflow-authoring/examples/bounded-semantic-retry.js +56 -0
- package/skills/workflow-authoring/examples/classify-and-act.js +67 -0
- package/skills/workflow-authoring/examples/fan-out-and-synthesize.js +47 -0
- package/skills/workflow-authoring/examples/generate-and-filter.js +68 -0
- package/skills/workflow-authoring/examples/loop-until-done.js +68 -0
- package/skills/workflow-authoring/examples/phased-budgets.js +75 -0
- package/skills/workflow-authoring/examples/saved-nested-workflows.js +44 -0
- package/skills/workflow-authoring/examples/structured-output.js +37 -0
- package/skills/workflow-authoring/examples/tournament.js +81 -0
- package/skills/workflow-authoring/examples/validated-gate.js +63 -0
- package/skills/workflow-authoring/references/capabilities.md +41 -0
- package/skills/workflow-authoring/references/capability-details.md +357 -0
- package/skills/workflow-authoring/references/common-helpers.md +4 -0
- package/skills/workflow-authoring/references/debugging.md +27 -0
- package/skills/workflow-authoring/references/focused-recipes.md +11 -0
- package/skills/workflow-authoring/references/helpers.md +9 -0
- package/skills/workflow-authoring/references/lifecycle.md +33 -0
- package/skills/workflow-authoring/references/pattern-selection.md +15 -0
- package/skills/workflow-authoring/references/quality-helpers.md +8 -0
- package/skills/workflow-authoring/references/registry-ownership.md +15 -0
- package/skills/workflow-authoring/references/retry-helper.md +5 -0
- package/skills/workflow-authoring/references/review.md +43 -0
- package/skills/workflow-authoring/references/runtime.md +27 -0
- package/skills/workflow-authoring/references/specialized-helpers.md +19 -0
- package/skills/workflow-authoring/references/versions.md +13 -0
- package/skills/workflow-patterns/SKILL.md +51 -0
- package/src/accept-workflow-guidance.ts +71 -0
- package/src/adversarial-review.ts +120 -0
- package/src/agent-history.ts +157 -0
- package/src/agent-registry.ts +221 -0
- package/src/agent.ts +929 -0
- package/src/builtin-commands.ts +286 -0
- package/src/builtin-workflows.ts +155 -0
- package/src/code-review.ts +183 -0
- package/src/config.ts +55 -0
- package/src/deep-research.ts +135 -0
- package/src/display.ts +367 -0
- package/src/effort-command.ts +87 -0
- package/src/enums.ts +77 -0
- package/src/errors.ts +199 -0
- package/src/extension-reload.ts +100 -0
- package/src/fs-persistence.ts +124 -0
- package/src/index.ts +176 -0
- package/src/logger.ts +88 -0
- package/src/model-routing.ts +73 -0
- package/src/model-spec.ts +309 -0
- package/src/model-tier-config.ts +296 -0
- package/src/run-persistence.ts +484 -0
- package/src/saved-commands.ts +115 -0
- package/src/shared-store.ts +228 -0
- package/src/structured-output.ts +47 -0
- package/src/task-panel.ts +490 -0
- package/src/usage-limit-scheduler.ts +432 -0
- package/src/web-tools.ts +124 -0
- package/src/workflow-authoring-coverage.ts +486 -0
- package/src/workflow-authoring-reference.ts +186 -0
- package/src/workflow-capability-contract.ts +806 -0
- package/src/workflow-commands.ts +287 -0
- package/src/workflow-comprehension.ts +1673 -0
- package/src/workflow-context-measurement.ts +262 -0
- package/src/workflow-control-tool.ts +238 -0
- package/src/workflow-delivery-choice.ts +69 -0
- package/src/workflow-editor.ts +444 -0
- package/src/workflow-manager.ts +1405 -0
- package/src/workflow-paths.ts +63 -0
- package/src/workflow-release-gate.ts +529 -0
- package/src/workflow-saved.ts +180 -0
- package/src/workflow-settings.ts +194 -0
- package/src/workflow-tool.ts +464 -0
- package/src/workflow-ui.ts +1789 -0
- package/src/workflow.ts +1615 -0
- package/src/workflows-models-command.ts +211 -0
- package/src/worktree.ts +76 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem layout for pi-dynamic-workflows state.
|
|
3
|
+
*
|
|
4
|
+
* New writes live under the user's workflow home so projects do not get
|
|
5
|
+
* scattered `.pi/workflows` directories. Project-scoped state is still isolated
|
|
6
|
+
* by a stable cwd-derived namespace.
|
|
7
|
+
*/
|
|
8
|
+
export declare const WORKFLOW_HOME_RELATIVE_DIR = ".pi/workflows";
|
|
9
|
+
export declare const WORKFLOW_PROJECTS_SUBDIR = "projects";
|
|
10
|
+
export interface WorkflowProjectPaths {
|
|
11
|
+
key: string;
|
|
12
|
+
rootDir: string;
|
|
13
|
+
runsDir: string;
|
|
14
|
+
savedDir: string;
|
|
15
|
+
settingsPath: string;
|
|
16
|
+
legacyRunsDir: string;
|
|
17
|
+
legacySavedDir: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function workflowHomeDir(): string;
|
|
20
|
+
export declare function workflowUserSavedDir(): string;
|
|
21
|
+
export declare function workflowProjectKey(cwd: string): string;
|
|
22
|
+
export declare function workflowProjectPaths(cwd: string): WorkflowProjectPaths;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem layout for pi-dynamic-workflows state.
|
|
3
|
+
*
|
|
4
|
+
* New writes live under the user's workflow home so projects do not get
|
|
5
|
+
* scattered `.pi/workflows` directories. Project-scoped state is still isolated
|
|
6
|
+
* by a stable cwd-derived namespace.
|
|
7
|
+
*/
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { basename, join, resolve } from "node:path";
|
|
11
|
+
import { WORKFLOW_RUNS_DIR, WORKFLOW_SAVED_DIR } from "./config.js";
|
|
12
|
+
export const WORKFLOW_HOME_RELATIVE_DIR = ".pi/workflows";
|
|
13
|
+
export const WORKFLOW_PROJECTS_SUBDIR = "projects";
|
|
14
|
+
export function workflowHomeDir() {
|
|
15
|
+
return join(homedir(), WORKFLOW_HOME_RELATIVE_DIR);
|
|
16
|
+
}
|
|
17
|
+
export function workflowUserSavedDir() {
|
|
18
|
+
return join(workflowHomeDir(), "saved");
|
|
19
|
+
}
|
|
20
|
+
export function workflowProjectKey(cwd) {
|
|
21
|
+
const projectPath = resolve(cwd);
|
|
22
|
+
const slug = sanitizePathSegment(basename(projectPath) || "project");
|
|
23
|
+
const hash = createHash("sha256").update(projectPath).digest("hex").slice(0, 12);
|
|
24
|
+
return `${slug}-${hash}`;
|
|
25
|
+
}
|
|
26
|
+
export function workflowProjectPaths(cwd) {
|
|
27
|
+
const key = workflowProjectKey(cwd);
|
|
28
|
+
const rootDir = join(workflowHomeDir(), WORKFLOW_PROJECTS_SUBDIR, key);
|
|
29
|
+
return {
|
|
30
|
+
key,
|
|
31
|
+
rootDir,
|
|
32
|
+
runsDir: join(rootDir, "runs"),
|
|
33
|
+
savedDir: join(rootDir, "saved"),
|
|
34
|
+
settingsPath: join(rootDir, "settings.json"),
|
|
35
|
+
legacyRunsDir: resolve(cwd, WORKFLOW_RUNS_DIR),
|
|
36
|
+
legacySavedDir: resolve(cwd, WORKFLOW_SAVED_DIR),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function sanitizePathSegment(value) {
|
|
40
|
+
const sanitized = value
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
43
|
+
.replace(/^-+|-+$/g, "")
|
|
44
|
+
.slice(0, 48);
|
|
45
|
+
return sanitized || "project";
|
|
46
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { WorkflowReleaseDiagnosticCode } from "./enums.js";
|
|
2
|
+
import { type WorkflowAuthoringCoverageEntry } from "./workflow-authoring-coverage.js";
|
|
3
|
+
import { type CAPABILITY_PUBLICATION_PATHS } from "./workflow-authoring-reference.js";
|
|
4
|
+
import { type WorkflowCapabilityDefinition } from "./workflow-capability-contract.js";
|
|
5
|
+
/** Re-exported stable diagnostic codes for release automation. */
|
|
6
|
+
export { WorkflowReleaseDiagnosticCode } from "./enums.js";
|
|
7
|
+
/** One actionable release alignment error or warning. */
|
|
8
|
+
export interface WorkflowReleaseDiagnostic {
|
|
9
|
+
code: WorkflowReleaseDiagnosticCode;
|
|
10
|
+
severity: "error" | "warning";
|
|
11
|
+
subject: string;
|
|
12
|
+
message: string;
|
|
13
|
+
}
|
|
14
|
+
type PublicationPath = (typeof CAPABILITY_PUBLICATION_PATHS)[number];
|
|
15
|
+
/** Inputs and test overrides for the model-free workflow release gate. */
|
|
16
|
+
export interface WorkflowReleaseCheckOptions {
|
|
17
|
+
root: string;
|
|
18
|
+
definition?: WorkflowCapabilityDefinition;
|
|
19
|
+
extensionVersion?: string;
|
|
20
|
+
skillVersion?: string;
|
|
21
|
+
publishableFiles: readonly string[];
|
|
22
|
+
publicationOverrides?: Readonly<Partial<Record<PublicationPath, string>>>;
|
|
23
|
+
contextMeasurement?: string;
|
|
24
|
+
guidanceBaseline?: string;
|
|
25
|
+
authoringCoverage?: readonly WorkflowAuthoringCoverageEntry[];
|
|
26
|
+
guidanceOverrides?: Readonly<Record<string, string>>;
|
|
27
|
+
}
|
|
28
|
+
/** Package-relative generated hash baseline for compact and detailed guidance. */
|
|
29
|
+
export declare const WORKFLOW_GUIDANCE_BASELINE_PATH = "docs/workflow-guidance-baseline.json";
|
|
30
|
+
/** Skill files that must be present in the publishable npm package. */
|
|
31
|
+
export declare const REQUIRED_WORKFLOW_PACKAGE_RESOURCES: readonly ["skills/workflow-authoring/SKILL.md", ...string[]];
|
|
32
|
+
/** Render deterministic hashes for provider-visible and on-demand guidance surfaces. */
|
|
33
|
+
export declare function renderWorkflowGuidanceBaseline(root: string): string;
|
|
34
|
+
/** Refresh the committed guidance hash baseline under root. */
|
|
35
|
+
export declare function writeWorkflowGuidanceBaseline(root: string): void;
|
|
36
|
+
/** Parse publishable paths from `npm pack --dry-run --json` without trusting external JSON shapes. */
|
|
37
|
+
export declare function parseNpmPackFilePaths(output: string): string[];
|
|
38
|
+
/** Return every model-free contract, package, documentation, and guidance alignment diagnostic. */
|
|
39
|
+
export declare function checkWorkflowRelease(options: WorkflowReleaseCheckOptions): WorkflowReleaseDiagnostic[];
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join, normalize, relative } from "node:path";
|
|
4
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
5
|
+
import { CapabilityClassification, CapabilitySupport, DiscoveryPlacement, WorkflowAuthoringProtection, WorkflowReleaseDiagnosticCode, } from "./enums.js";
|
|
6
|
+
import { WORKFLOW_AUTHORING_COVERAGE, WORKFLOW_AUTHORING_FROZEN_FILES, WORKFLOW_AUTHORING_PATTERN_IDS, WORKFLOW_AUTHORING_RECIPE_IDS, WORKFLOW_COMPREHENSION_SCENARIO_IDS, } from "./workflow-authoring-coverage.js";
|
|
7
|
+
import { checkWorkflowCapabilityPublications, } from "./workflow-authoring-reference.js";
|
|
8
|
+
import { WORKFLOW_CAPABILITY_DEFINITION } from "./workflow-capability-contract.js";
|
|
9
|
+
import { checkWorkflowContextMeasurement, WORKFLOW_CONTEXT_MEASUREMENT_PATH } from "./workflow-context-measurement.js";
|
|
10
|
+
import { createWorkflowTool } from "./workflow-tool.js";
|
|
11
|
+
/** Re-exported stable diagnostic codes for release automation. */
|
|
12
|
+
export { WorkflowReleaseDiagnosticCode } from "./enums.js";
|
|
13
|
+
const SKILL_ROOT = "skills/workflow-authoring";
|
|
14
|
+
/** Package-relative generated hash baseline for compact and detailed guidance. */
|
|
15
|
+
export const WORKFLOW_GUIDANCE_BASELINE_PATH = "docs/workflow-guidance-baseline.json";
|
|
16
|
+
const FOCUSED_REFERENCES = [
|
|
17
|
+
"capabilities",
|
|
18
|
+
"capability-details",
|
|
19
|
+
"runtime",
|
|
20
|
+
"helpers",
|
|
21
|
+
"common-helpers",
|
|
22
|
+
"quality-helpers",
|
|
23
|
+
"retry-helper",
|
|
24
|
+
"specialized-helpers",
|
|
25
|
+
"lifecycle",
|
|
26
|
+
"versions",
|
|
27
|
+
"pattern-selection",
|
|
28
|
+
"focused-recipes",
|
|
29
|
+
"registry-ownership",
|
|
30
|
+
"review",
|
|
31
|
+
"debugging",
|
|
32
|
+
];
|
|
33
|
+
const PATTERNS = [
|
|
34
|
+
"classify-and-act",
|
|
35
|
+
"fan-out-and-synthesize",
|
|
36
|
+
"adversarial-verification",
|
|
37
|
+
"generate-and-filter",
|
|
38
|
+
"tournament",
|
|
39
|
+
"loop-until-done",
|
|
40
|
+
];
|
|
41
|
+
const RECIPES = ["phased-budgets", "saved-nested-workflows", "bounded-semantic-retry", "structured-output"];
|
|
42
|
+
/** Skill files that must be present in the publishable npm package. */
|
|
43
|
+
export const REQUIRED_WORKFLOW_PACKAGE_RESOURCES = [
|
|
44
|
+
`${SKILL_ROOT}/SKILL.md`,
|
|
45
|
+
...FOCUSED_REFERENCES.map((name) => `${SKILL_ROOT}/references/${name}.md`),
|
|
46
|
+
...PATTERNS.map((name) => `${SKILL_ROOT}/examples/${name}.js`),
|
|
47
|
+
...RECIPES.map((name) => `${SKILL_ROOT}/examples/${name}.js`),
|
|
48
|
+
];
|
|
49
|
+
function diagnostic(code, subject, message, severity = "error") {
|
|
50
|
+
return { code, severity, subject, message };
|
|
51
|
+
}
|
|
52
|
+
function skillVersion(root) {
|
|
53
|
+
const skill = readFileSync(join(root, SKILL_ROOT, "SKILL.md"), "utf8");
|
|
54
|
+
return /^\s{2}version:\s*["']?([^"'\s]+)["']?\s*$/m.exec(skill)?.[1] ?? null;
|
|
55
|
+
}
|
|
56
|
+
function anchorExists(markdown, anchor) {
|
|
57
|
+
if (markdown.includes(`<a id="${anchor}"></a>`))
|
|
58
|
+
return true;
|
|
59
|
+
return markdown
|
|
60
|
+
.split("\n")
|
|
61
|
+
.filter((line) => /^#{1,6} /.test(line))
|
|
62
|
+
.map((line) => line
|
|
63
|
+
.replace(/^#{1,6} /, "")
|
|
64
|
+
.toLowerCase()
|
|
65
|
+
.replace(/[^a-z0-9 -]/g, "")
|
|
66
|
+
.trim()
|
|
67
|
+
.replace(/ +/g, "-"))
|
|
68
|
+
.includes(anchor);
|
|
69
|
+
}
|
|
70
|
+
function validateVersions(definition, extensionVersion, installedSkillVersion) {
|
|
71
|
+
const diagnostics = [];
|
|
72
|
+
const versions = [
|
|
73
|
+
["contract extension", definition.versions.extension],
|
|
74
|
+
["contract content", definition.versions.content.version],
|
|
75
|
+
["installed skill", installedSkillVersion],
|
|
76
|
+
];
|
|
77
|
+
for (const [subject, actual] of versions) {
|
|
78
|
+
if (actual !== extensionVersion) {
|
|
79
|
+
diagnostics.push(diagnostic(WorkflowReleaseDiagnosticCode.INCOMPATIBLE_VERSION, subject, `${subject} version ${actual ?? "<missing>"} must match extension version ${extensionVersion}.`));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (definition.versions.format.kind !== "present-at" || !/^1(?:\.|$)/.test(definition.versions.format.version)) {
|
|
83
|
+
diagnostics.push(diagnostic(WorkflowReleaseDiagnosticCode.INCOMPATIBLE_VERSION, "contract format", `Contract format ${definition.versions.format.version} is incompatible with supported format major 1.`));
|
|
84
|
+
}
|
|
85
|
+
return diagnostics;
|
|
86
|
+
}
|
|
87
|
+
function validateCapabilityLinks(root, definition) {
|
|
88
|
+
const diagnostics = [];
|
|
89
|
+
for (const capability of definition.capabilities) {
|
|
90
|
+
if (capability.support === CapabilitySupport.SUPPORTED &&
|
|
91
|
+
capability.discovery !== DiscoveryPlacement.NONE &&
|
|
92
|
+
capability.behaviorEvidence.length === 0) {
|
|
93
|
+
diagnostics.push(diagnostic(WorkflowReleaseDiagnosticCode.MISSING_BEHAVIOR_EVIDENCE, capability.id, `Advertised capability ${capability.id} has no behavior-test evidence.`));
|
|
94
|
+
}
|
|
95
|
+
for (const evidence of capability.behaviorEvidence) {
|
|
96
|
+
if (!existsSync(join(root, evidence))) {
|
|
97
|
+
diagnostics.push(diagnostic(WorkflowReleaseDiagnosticCode.UNRESOLVED_BEHAVIOR_EVIDENCE, evidence, `Capability ${capability.id} references missing behavior test ${evidence}.`));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (capability.staticReference) {
|
|
101
|
+
const { path, anchor } = capability.staticReference;
|
|
102
|
+
const absolute = join(root, path);
|
|
103
|
+
const subject = `${path}#${anchor}`;
|
|
104
|
+
if (!existsSync(absolute) || !anchorExists(readFileSync(absolute, "utf8"), anchor)) {
|
|
105
|
+
diagnostics.push(diagnostic(WorkflowReleaseDiagnosticCode.BROKEN_CONTRACT_REFERENCE, subject, `Capability ${capability.id} references unresolved installed documentation ${subject}.`));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return diagnostics;
|
|
110
|
+
}
|
|
111
|
+
function validateAuthoringCoverage(root, definition, coverage, guidanceOverrides = {}) {
|
|
112
|
+
const expectedIds = [
|
|
113
|
+
...definition.capabilities
|
|
114
|
+
.filter(({ discovery, support }) => discovery !== DiscoveryPlacement.NONE && support !== CapabilitySupport.INTERNAL)
|
|
115
|
+
.map(({ id }) => id),
|
|
116
|
+
...WORKFLOW_AUTHORING_PATTERN_IDS,
|
|
117
|
+
...WORKFLOW_AUTHORING_RECIPE_IDS,
|
|
118
|
+
];
|
|
119
|
+
const observed = new Set(coverage.map(({ id }) => id));
|
|
120
|
+
const diagnostics = expectedIds
|
|
121
|
+
.filter((id) => !observed.has(id))
|
|
122
|
+
.map((id) => diagnostic(WorkflowReleaseDiagnosticCode.MISSING_AUTHORING_COVERAGE, id, `Stable workflow authoring surface ${id} is absent from the capability coverage manifest.`));
|
|
123
|
+
const knownScenarioIds = new Set(WORKFLOW_COMPREHENSION_SCENARIO_IDS);
|
|
124
|
+
for (const entry of coverage.filter(({ protection }) => protection === WorkflowAuthoringProtection.BEHAVIORALLY_COVERED)) {
|
|
125
|
+
if (entry.comprehensionScenarios.length === 0 ||
|
|
126
|
+
entry.comprehensionScenarios.some((scenarioId) => !knownScenarioIds.has(scenarioId))) {
|
|
127
|
+
diagnostics.push(diagnostic(WorkflowReleaseDiagnosticCode.UNKNOWN_COMPREHENSION_SCENARIO, entry.id, `Behaviorally covered authoring surface ${entry.id} references a missing comprehension scenario.`));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
for (const entry of coverage.filter(({ protection }) => protection === WorkflowAuthoringProtection.GUIDANCE_FROZEN)) {
|
|
131
|
+
if (entry.protectedGuidance.length === 0) {
|
|
132
|
+
diagnostics.push(diagnostic(WorkflowReleaseDiagnosticCode.UNPROTECTED_AUTHORING_GUIDANCE, entry.id, `Untested authoring surface ${entry.id} has no frozen guidance or routing pointer.`));
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const drifted = entry.protectedGuidance.find(({ path, anchor, requiredText }) => {
|
|
136
|
+
const absolute = join(root, path);
|
|
137
|
+
if (!existsSync(absolute) && guidanceOverrides[path] === undefined)
|
|
138
|
+
return true;
|
|
139
|
+
const source = guidanceOverrides[path] ?? readFileSync(absolute, "utf8");
|
|
140
|
+
return ((anchor !== undefined && !anchorExists(source, anchor)) ||
|
|
141
|
+
(requiredText !== undefined && !source.includes(requiredText)));
|
|
142
|
+
});
|
|
143
|
+
if (drifted) {
|
|
144
|
+
const absolute = join(root, drifted.path);
|
|
145
|
+
const source = !existsSync(absolute) && guidanceOverrides[drifted.path] === undefined
|
|
146
|
+
? null
|
|
147
|
+
: (guidanceOverrides[drifted.path] ?? readFileSync(absolute, "utf8"));
|
|
148
|
+
const failedChecks = [];
|
|
149
|
+
if (source === null) {
|
|
150
|
+
failedChecks.push("protected file");
|
|
151
|
+
}
|
|
152
|
+
if (source !== null && drifted.anchor !== undefined && !anchorExists(source, drifted.anchor)) {
|
|
153
|
+
failedChecks.push("required anchor");
|
|
154
|
+
}
|
|
155
|
+
if (source !== null && drifted.requiredText !== undefined && !source.includes(drifted.requiredText)) {
|
|
156
|
+
failedChecks.push("required text");
|
|
157
|
+
}
|
|
158
|
+
diagnostics.push(diagnostic(WorkflowReleaseDiagnosticCode.PROTECTED_GUIDANCE_DRIFT, entry.id, `Protected guidance for ${entry.id} no longer matches the ${failedChecks.join(" and ")} in ${drifted.path}. Restore it to undo an accidental change. For an intentional change, inspect the coverage manifest and relevant behavioral/provider evidence before deliberately updating the corresponding anchor/text in src/workflow-authoring-coverage.ts. See CONTRIBUTING.md#protected-workflow-authoring-guidance.`));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return diagnostics;
|
|
162
|
+
}
|
|
163
|
+
function validateFrozenGuidanceFiles(root, guidanceOverrides = {}) {
|
|
164
|
+
return WORKFLOW_AUTHORING_FROZEN_FILES.flatMap(({ path, sha256: expected }) => {
|
|
165
|
+
const absolute = join(root, path);
|
|
166
|
+
if (!existsSync(absolute) && guidanceOverrides[path] === undefined) {
|
|
167
|
+
return [
|
|
168
|
+
diagnostic(WorkflowReleaseDiagnosticCode.PROTECTED_GUIDANCE_DRIFT, path, `Protected workflow-authoring file is missing: ${path}. Restore an accidental deletion. An intentional removal requires review of the coverage manifest and relevant behavioral/provider evidence, followed by deliberate updates to authoring coverage and package resources. See CONTRIBUTING.md#protected-workflow-authoring-guidance.`),
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
const source = guidanceOverrides[path] ?? readFileSync(absolute, "utf8");
|
|
172
|
+
return sha256(source) === expected
|
|
173
|
+
? []
|
|
174
|
+
: [
|
|
175
|
+
diagnostic(WorkflowReleaseDiagnosticCode.PROTECTED_GUIDANCE_DRIFT, path, `Protected workflow-authoring file changed: ${path}. Its SHA-256 is an explicit review checkpoint for guidance with partial behavioral coverage. Revert accidental changes. For an intentional change, inspect the coverage manifest and relevant behavioral tests, plus provider evidence when needed, then run npm run guidance:accept -- ${path}. This updates the matching sha256 in WORKFLOW_AUTHORING_FROZEN_FILES (src/workflow-authoring-coverage.ts). See CONTRIBUTING.md#protected-workflow-authoring-guidance.`),
|
|
176
|
+
];
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
function validateToolInputs(definition) {
|
|
180
|
+
const { parameters } = createWorkflowTool();
|
|
181
|
+
const properties = isRecord(parameters) && isRecord(parameters.properties) ? parameters.properties : {};
|
|
182
|
+
const observed = new Set(Object.keys(properties));
|
|
183
|
+
const declared = new Set(definition.capabilities
|
|
184
|
+
.filter((capability) => capability.classification === CapabilityClassification.WORKFLOW_TOOL_INPUT)
|
|
185
|
+
.map((capability) => capability.label));
|
|
186
|
+
const diagnostics = [];
|
|
187
|
+
for (const name of declared) {
|
|
188
|
+
if (!observed.has(name)) {
|
|
189
|
+
diagnostics.push(diagnostic(WorkflowReleaseDiagnosticCode.TOOL_INPUT_MISMATCH, name, `Declared workflow-tool input ${name} is absent from the runtime schema.`));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
for (const name of observed) {
|
|
193
|
+
if (!declared.has(name)) {
|
|
194
|
+
diagnostics.push(diagnostic(WorkflowReleaseDiagnosticCode.TOOL_INPUT_MISMATCH, name, `Runtime schema exposes undeclared workflow-tool input ${name}.`));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const tokenBudget = definition.capabilities.find((capability) => capability.classification === CapabilityClassification.WORKFLOW_TOOL_INPUT && capability.label === "tokenBudget");
|
|
198
|
+
const tokenBudgetSchema = properties.tokenBudget;
|
|
199
|
+
const observedTokenBudget = isRecord(tokenBudgetSchema) && typeof tokenBudgetSchema.description === "string"
|
|
200
|
+
? tokenBudgetSchema.description
|
|
201
|
+
: "";
|
|
202
|
+
const contractCallsSoft = tokenBudget?.constraints.some((constraint) => /soft/i.test(constraint)) ?? false;
|
|
203
|
+
const contractCallsHard = tokenBudget?.constraints.some((constraint) => /hard/i.test(constraint)) ?? false;
|
|
204
|
+
const proseCallsSoft = /soft/i.test(observedTokenBudget);
|
|
205
|
+
const proseCallsHard = /hard/i.test(observedTokenBudget);
|
|
206
|
+
if ((contractCallsSoft && proseCallsHard) || (contractCallsHard && proseCallsSoft)) {
|
|
207
|
+
diagnostics.push(diagnostic(WorkflowReleaseDiagnosticCode.RUNTIME_CONSTRAINT_DISAGREEMENT, "tokenBudget", `Contract constraints and provider-visible tool prose disagree about whether tokenBudget is a soft or hard gate. Contract: ${tokenBudget?.constraints.join("; ")}. Tool prose: ${observedTokenBudget}`));
|
|
208
|
+
}
|
|
209
|
+
return diagnostics;
|
|
210
|
+
}
|
|
211
|
+
function isRecord(value) {
|
|
212
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
213
|
+
}
|
|
214
|
+
function sha256(value) {
|
|
215
|
+
return createHash("sha256").update(value).digest("hex");
|
|
216
|
+
}
|
|
217
|
+
/** Render deterministic hashes for provider-visible and on-demand guidance surfaces. */
|
|
218
|
+
export function renderWorkflowGuidanceBaseline(root) {
|
|
219
|
+
const tool = createWorkflowTool();
|
|
220
|
+
const compact = JSON.stringify({
|
|
221
|
+
description: tool.description,
|
|
222
|
+
parameters: tool.parameters,
|
|
223
|
+
promptSnippet: tool.promptSnippet,
|
|
224
|
+
promptGuidelines: tool.promptGuidelines,
|
|
225
|
+
});
|
|
226
|
+
const detailedPaths = [
|
|
227
|
+
`${SKILL_ROOT}/SKILL.md`,
|
|
228
|
+
...FOCUSED_REFERENCES.filter((name) => name !== "capabilities" && name !== "capability-details").map((name) => `${SKILL_ROOT}/references/${name}.md`),
|
|
229
|
+
];
|
|
230
|
+
const detailed = detailedPaths.map((path) => `${path}\n${readFileSync(join(root, path), "utf8")}`).join("\n");
|
|
231
|
+
return `${JSON.stringify({
|
|
232
|
+
formatVersion: 1,
|
|
233
|
+
algorithm: "sha256",
|
|
234
|
+
surfaces: {
|
|
235
|
+
compactGuidance: sha256(compact),
|
|
236
|
+
detailedProse: sha256(detailed),
|
|
237
|
+
},
|
|
238
|
+
}, null, 2)}\n`;
|
|
239
|
+
}
|
|
240
|
+
/** Refresh the committed guidance hash baseline under root. */
|
|
241
|
+
export function writeWorkflowGuidanceBaseline(root) {
|
|
242
|
+
writeFileSync(join(root, WORKFLOW_GUIDANCE_BASELINE_PATH), renderWorkflowGuidanceBaseline(root));
|
|
243
|
+
}
|
|
244
|
+
function validateGuidanceBaseline(root, actual) {
|
|
245
|
+
const committed = actual ?? readFileSync(join(root, WORKFLOW_GUIDANCE_BASELINE_PATH), "utf8");
|
|
246
|
+
if (committed === renderWorkflowGuidanceBaseline(root))
|
|
247
|
+
return [];
|
|
248
|
+
return [
|
|
249
|
+
diagnostic(WorkflowReleaseDiagnosticCode.NON_CONTRACTUAL_PROSE_DRIFT, WORKFLOW_GUIDANCE_BASELINE_PATH, `Compact guidance or detailed hand-written prose changed; review intent and refresh ${WORKFLOW_GUIDANCE_BASELINE_PATH} if intentional.`, "warning"),
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
function validatePackage(root, publishableFiles) {
|
|
253
|
+
const diagnostics = [];
|
|
254
|
+
const files = new Set(publishableFiles);
|
|
255
|
+
for (const resource of REQUIRED_WORKFLOW_PACKAGE_RESOURCES) {
|
|
256
|
+
if (!files.has(resource)) {
|
|
257
|
+
diagnostics.push(diagnostic(WorkflowReleaseDiagnosticCode.MISSING_PACKAGE_RESOURCE, resource, `Publishable package omitted required workflow resource ${resource}.`));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
for (const sourcePath of publishableFiles.filter((path) => path.startsWith(`${SKILL_ROOT}/`) && path.endsWith(".md"))) {
|
|
261
|
+
const source = readFileSync(join(root, sourcePath), "utf8");
|
|
262
|
+
for (const match of source.matchAll(/\[[^\]]+\]\(([^)#]+)(?:#([^)]+))?\)/g)) {
|
|
263
|
+
const target = normalize(join(dirname(sourcePath), match[1]));
|
|
264
|
+
const anchor = match[2];
|
|
265
|
+
const outsidePackage = relative(".", target).startsWith("..");
|
|
266
|
+
const targetMissing = !files.has(target);
|
|
267
|
+
const targetSource = !targetMissing && anchor ? readFileSync(join(root, target), "utf8") : null;
|
|
268
|
+
if (outsidePackage || targetMissing || (anchor && targetSource !== null && !anchorExists(targetSource, anchor))) {
|
|
269
|
+
const subject = `${sourcePath} -> ${target}${anchor ? `#${anchor}` : ""}`;
|
|
270
|
+
diagnostics.push(diagnostic(WorkflowReleaseDiagnosticCode.BROKEN_PACKAGE_LINK, subject, `Packaged workflow skill link does not resolve: ${subject}.`));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return diagnostics;
|
|
275
|
+
}
|
|
276
|
+
/** Parse publishable paths from `npm pack --dry-run --json` without trusting external JSON shapes. */
|
|
277
|
+
export function parseNpmPackFilePaths(output) {
|
|
278
|
+
const parsed = JSON.parse(output);
|
|
279
|
+
if (!Array.isArray(parsed)) {
|
|
280
|
+
return [];
|
|
281
|
+
}
|
|
282
|
+
const first = parsed[0];
|
|
283
|
+
if (!isRecord(first) || !Array.isArray(first.files)) {
|
|
284
|
+
return [];
|
|
285
|
+
}
|
|
286
|
+
return first.files.flatMap((file) => (isRecord(file) && typeof file.path === "string" ? [file.path] : []));
|
|
287
|
+
}
|
|
288
|
+
/** Return every model-free contract, package, documentation, and guidance alignment diagnostic. */
|
|
289
|
+
export function checkWorkflowRelease(options) {
|
|
290
|
+
const definition = options.definition ?? WORKFLOW_CAPABILITY_DEFINITION;
|
|
291
|
+
const extensionVersion = options.extensionVersion ?? packageJson.version;
|
|
292
|
+
const installedSkillVersion = options.skillVersion ?? skillVersion(options.root);
|
|
293
|
+
const diagnostics = [
|
|
294
|
+
...validateVersions(definition, extensionVersion, installedSkillVersion),
|
|
295
|
+
...validateCapabilityLinks(options.root, definition),
|
|
296
|
+
...validateAuthoringCoverage(options.root, definition, options.authoringCoverage ?? WORKFLOW_AUTHORING_COVERAGE, options.guidanceOverrides),
|
|
297
|
+
...validateFrozenGuidanceFiles(options.root, options.guidanceOverrides),
|
|
298
|
+
...validateToolInputs(definition),
|
|
299
|
+
...validatePackage(options.root, options.publishableFiles),
|
|
300
|
+
...validateGuidanceBaseline(options.root, options.guidanceBaseline),
|
|
301
|
+
];
|
|
302
|
+
for (const path of checkWorkflowCapabilityPublications(options.root, options.publicationOverrides)) {
|
|
303
|
+
diagnostics.push(diagnostic(WorkflowReleaseDiagnosticCode.STALE_GENERATED_SURFACE, path, `Generated workflow capability publication is stale: ${path}.`));
|
|
304
|
+
}
|
|
305
|
+
if (!checkWorkflowContextMeasurement(options.root, options.contextMeasurement)) {
|
|
306
|
+
diagnostics.push(diagnostic(WorkflowReleaseDiagnosticCode.STALE_GENERATED_SURFACE, WORKFLOW_CONTEXT_MEASUREMENT_PATH, `Generated workflow context measurement is stale: ${WORKFLOW_CONTEXT_MEASUREMENT_PATH}.`));
|
|
307
|
+
}
|
|
308
|
+
return diagnostics;
|
|
309
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Save and load reusable workflow commands.
|
|
3
|
+
*/
|
|
4
|
+
import { type PersistenceFsLayer } from "./fs-persistence.js";
|
|
5
|
+
export interface SavedWorkflow {
|
|
6
|
+
/** Command name (filename without extension). */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Human-readable description. */
|
|
9
|
+
description: string;
|
|
10
|
+
/** The workflow script. */
|
|
11
|
+
script: string;
|
|
12
|
+
/** Optional parameter schema for parameterized workflows. */
|
|
13
|
+
parameters?: Record<string, {
|
|
14
|
+
type: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
required?: boolean;
|
|
17
|
+
default?: unknown;
|
|
18
|
+
}>;
|
|
19
|
+
/** Where this workflow is saved. */
|
|
20
|
+
location: "project" | "user";
|
|
21
|
+
/** Full file path. */
|
|
22
|
+
path: string;
|
|
23
|
+
/** When it was saved. */
|
|
24
|
+
savedAt: string;
|
|
25
|
+
}
|
|
26
|
+
export interface WorkflowStorage {
|
|
27
|
+
/** Save a workflow. */
|
|
28
|
+
save(workflow: Omit<SavedWorkflow, "path" | "savedAt">, location?: "project" | "user"): SavedWorkflow;
|
|
29
|
+
/** Load a workflow by name. */
|
|
30
|
+
load(name: string): SavedWorkflow | null;
|
|
31
|
+
/** List all saved workflows. */
|
|
32
|
+
list(): SavedWorkflow[];
|
|
33
|
+
/** Delete a saved workflow. */
|
|
34
|
+
delete(name: string, location?: "project" | "user"): boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare function isSafeSavedWorkflowName(name: string): boolean;
|
|
37
|
+
export declare function assertSafeSavedWorkflowName(name: string): void;
|
|
38
|
+
export declare function createWorkflowStorage(cwd: string, fsOverride?: Partial<PersistenceFsLayer>): WorkflowStorage;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Save and load reusable workflow commands.
|
|
3
|
+
*/
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { ensureDir as ensureDirFs, listJsonFilesSafe, readJsonWithBackupRecovery, resolvePersistenceFs, unlinkIfExistsSafe, writeJsonAtomicWithBackup, } from "./fs-persistence.js";
|
|
6
|
+
import { workflowProjectPaths, workflowUserSavedDir } from "./workflow-paths.js";
|
|
7
|
+
export function isSafeSavedWorkflowName(name) {
|
|
8
|
+
return (name.length > 0 &&
|
|
9
|
+
name.length <= 128 &&
|
|
10
|
+
name.trim() === name &&
|
|
11
|
+
name !== "." &&
|
|
12
|
+
name !== ".." &&
|
|
13
|
+
!/[/\\\0]/.test(name));
|
|
14
|
+
}
|
|
15
|
+
export function assertSafeSavedWorkflowName(name) {
|
|
16
|
+
if (!isSafeSavedWorkflowName(name)) {
|
|
17
|
+
throw new Error("Saved workflow name must be a non-empty path-safe name without slashes.");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function createWorkflowStorage(cwd, fsOverride) {
|
|
21
|
+
const fs = resolvePersistenceFs(fsOverride);
|
|
22
|
+
const paths = workflowProjectPaths(cwd);
|
|
23
|
+
const projectDir = paths.savedDir;
|
|
24
|
+
const legacyProjectDir = paths.legacySavedDir;
|
|
25
|
+
const userDir = workflowUserSavedDir();
|
|
26
|
+
const ensureDir = (dir) => ensureDirFs(fs, dir);
|
|
27
|
+
const workflowPath = (name, location) => {
|
|
28
|
+
assertSafeSavedWorkflowName(name);
|
|
29
|
+
const dir = location === "project" ? projectDir : userDir;
|
|
30
|
+
return join(dir, `${name}.json`);
|
|
31
|
+
};
|
|
32
|
+
const legacyProjectWorkflowPath = (name) => {
|
|
33
|
+
assertSafeSavedWorkflowName(name);
|
|
34
|
+
return join(legacyProjectDir, `${name}.json`);
|
|
35
|
+
};
|
|
36
|
+
// Same atomic-write-with-backup + corrupt-file recovery contract as
|
|
37
|
+
// run-persistence.ts (see fs-persistence.ts) — a saved workflow is a
|
|
38
|
+
// user-authored artifact just as worth protecting from a crash mid-write
|
|
39
|
+
// or a truncated file as a run's resumable state is.
|
|
40
|
+
const loadFromFile = (path, location) => {
|
|
41
|
+
const data = readJsonWithBackupRecovery(fs, path);
|
|
42
|
+
if (!data || typeof data !== "object" || !isSafeSavedWorkflowName(data.name ?? "")) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
...data,
|
|
47
|
+
location,
|
|
48
|
+
path,
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
save(workflow, location = "project") {
|
|
53
|
+
assertSafeSavedWorkflowName(workflow.name);
|
|
54
|
+
const dir = location === "project" ? projectDir : userDir;
|
|
55
|
+
ensureDir(dir);
|
|
56
|
+
const path = workflowPath(workflow.name, location);
|
|
57
|
+
const saved = {
|
|
58
|
+
...workflow,
|
|
59
|
+
location,
|
|
60
|
+
path,
|
|
61
|
+
savedAt: new Date().toISOString(),
|
|
62
|
+
};
|
|
63
|
+
writeJsonAtomicWithBackup(fs, path, saved);
|
|
64
|
+
return saved;
|
|
65
|
+
},
|
|
66
|
+
load(name) {
|
|
67
|
+
if (!isSafeSavedWorkflowName(name))
|
|
68
|
+
return null;
|
|
69
|
+
// Project takes precedence over user
|
|
70
|
+
const projectPath = workflowPath(name, "project");
|
|
71
|
+
const project = loadFromFile(projectPath, "project");
|
|
72
|
+
if (project)
|
|
73
|
+
return project;
|
|
74
|
+
const legacyProject = loadFromFile(legacyProjectWorkflowPath(name), "project");
|
|
75
|
+
if (legacyProject)
|
|
76
|
+
return legacyProject;
|
|
77
|
+
const userPath = workflowPath(name, "user");
|
|
78
|
+
return loadFromFile(userPath, "user");
|
|
79
|
+
},
|
|
80
|
+
list() {
|
|
81
|
+
const workflows = [];
|
|
82
|
+
const seen = new Set();
|
|
83
|
+
const addDir = (dir, location) => {
|
|
84
|
+
// A missing or unreadable directory (not yet created, deleted
|
|
85
|
+
// mid-race, permission-denied) degrades to "no files" here — same
|
|
86
|
+
// guard run-persistence.ts's list() uses — rather than throwing and
|
|
87
|
+
// taking down the whole listing over one bad storage location.
|
|
88
|
+
for (const file of listJsonFilesSafe(fs, dir)) {
|
|
89
|
+
const wf = loadFromFile(join(dir, file), location);
|
|
90
|
+
if (wf && !seen.has(wf.name)) {
|
|
91
|
+
seen.add(wf.name);
|
|
92
|
+
workflows.push(wf);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
// Priority order mirrors load(): project > legacy project > user.
|
|
97
|
+
addDir(projectDir, "project");
|
|
98
|
+
addDir(legacyProjectDir, "project");
|
|
99
|
+
addDir(userDir, "user");
|
|
100
|
+
return workflows.sort((a, b) => a.name.localeCompare(b.name));
|
|
101
|
+
},
|
|
102
|
+
delete(name, location) {
|
|
103
|
+
if (!isSafeSavedWorkflowName(name))
|
|
104
|
+
return false;
|
|
105
|
+
const locations = location ? [location] : ["project", "user"];
|
|
106
|
+
let deleted = false;
|
|
107
|
+
for (const loc of locations) {
|
|
108
|
+
const path = workflowPath(name, loc);
|
|
109
|
+
// Clean up the .bak sidecar too, mirroring run-persistence.ts's delete()
|
|
110
|
+
// (sidecar cleanup does not by itself count as "deleted the workflow").
|
|
111
|
+
unlinkIfExistsSafe(fs, `${path}.bak`);
|
|
112
|
+
if (unlinkIfExistsSafe(fs, path)) {
|
|
113
|
+
deleted = true;
|
|
114
|
+
}
|
|
115
|
+
if (loc === "project") {
|
|
116
|
+
const legacyPath = legacyProjectWorkflowPath(name);
|
|
117
|
+
unlinkIfExistsSafe(fs, `${legacyPath}.bak`);
|
|
118
|
+
if (unlinkIfExistsSafe(fs, legacyPath)) {
|
|
119
|
+
deleted = true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return deleted;
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-level settings for pi-dynamic-workflows.
|
|
3
|
+
*
|
|
4
|
+
* Stored separately from Pi's own settings.json so extension preferences remain
|
|
5
|
+
* stable without depending on host-internal config shape.
|
|
6
|
+
*/
|
|
7
|
+
export interface WorkflowSettings {
|
|
8
|
+
keywordTriggerEnabled?: boolean;
|
|
9
|
+
/** Literal keyword that arms workflows mode from interactive input. */
|
|
10
|
+
keywordTriggerWord?: string;
|
|
11
|
+
defaultAgentTimeoutMs?: number | null;
|
|
12
|
+
/**
|
|
13
|
+
* Default hard token budget applied to runs that don't pass their own
|
|
14
|
+
* `tokenBudget` (#68). null explicitly means "no budget" (useful in a
|
|
15
|
+
* project override to cancel a global budget); omitted also means no budget.
|
|
16
|
+
*/
|
|
17
|
+
defaultTokenBudget?: number | null;
|
|
18
|
+
/** Default max concurrent agents per run. Clamped to the runtime maximum. */
|
|
19
|
+
defaultConcurrency?: number;
|
|
20
|
+
/** Default retry attempts after recoverable agent failures. */
|
|
21
|
+
defaultAgentRetries?: number;
|
|
22
|
+
/** Bottom task-panel display mode: "compact" (default, one line per run) | "detailed". */
|
|
23
|
+
progressPanelMode?: "compact" | "detailed";
|
|
24
|
+
/** Max agents shown per phase in detailed progress mode (default 8). */
|
|
25
|
+
progressPanelMaxAgents?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Persist each workflow subagent transcript as a real pi session file under
|
|
28
|
+
* the standard sessions directory (~/.pi/agent/sessions/<encoded-cwd>/),
|
|
29
|
+
* keyed by the project cwd. Default false: subagent sessions stay in-memory
|
|
30
|
+
* and only the compacted history embedded in the run JSON survives.
|
|
31
|
+
*/
|
|
32
|
+
persistAgentSessions?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Character cap on a delivered background-run result's JSON-dump fallback
|
|
35
|
+
* before truncation (default 400). String results and `verdict`/`report`/
|
|
36
|
+
* `summary`/`synthesis` fields are never truncated.
|
|
37
|
+
*/
|
|
38
|
+
deliveredResultMaxChars?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Extra tool names to deny in workflow subagent sessions, on top of the
|
|
41
|
+
* always-on `workflow`/`workflow_control` defaults (#107). Use it to block
|
|
42
|
+
* other recursive-orchestration tools you have installed (e.g. a pi-subagents
|
|
43
|
+
* tool) so a subagent can't fan out through them.
|
|
44
|
+
*/
|
|
45
|
+
excludeSubagentTools?: string[];
|
|
46
|
+
}
|
|
47
|
+
export interface WorkflowSettingsStore {
|
|
48
|
+
load(): WorkflowSettings;
|
|
49
|
+
save(settings: WorkflowSettings): void;
|
|
50
|
+
}
|
|
51
|
+
export interface WorkflowSettingsOptions {
|
|
52
|
+
/** Explicit settings path, primarily for tests and migrations. */
|
|
53
|
+
settingsPath?: string;
|
|
54
|
+
/** Project cwd whose project-level settings should override global settings. */
|
|
55
|
+
cwd?: string;
|
|
56
|
+
/** Explicit project settings path, primarily for tests. */
|
|
57
|
+
projectSettingsPath?: string;
|
|
58
|
+
/** Save destination when using saveWorkflowSettings with cwd. Default: global. */
|
|
59
|
+
scope?: "global" | "project";
|
|
60
|
+
}
|
|
61
|
+
/** Path to the user-level workflow settings JSON file (~/.pi/workflows/settings.json). */
|
|
62
|
+
export declare function getWorkflowSettingsPath(): string;
|
|
63
|
+
/** Path to this project's optional workflow settings override. */
|
|
64
|
+
export declare function getWorkflowProjectSettingsPath(cwd: string): string;
|
|
65
|
+
/** Load settings from disk. Missing, corrupt, or invalid files resolve to {}. */
|
|
66
|
+
export declare function loadWorkflowSettings(settingsPathOrOptions?: string | WorkflowSettingsOptions): WorkflowSettings;
|
|
67
|
+
/** Merge known settings into the user-level settings file. */
|
|
68
|
+
export declare function saveWorkflowSettings(settings: WorkflowSettings, settingsPathOrOptions?: string | WorkflowSettingsOptions): void;
|
|
69
|
+
/** Save a global preference and update an existing project override if one is present. */
|
|
70
|
+
export declare function saveWorkflowSettingsForCwd(settings: WorkflowSettings, cwd: string): void;
|