patchwork-os 0.2.0-alpha.34 → 0.2.0-alpha.36
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/README.md +202 -93
- package/deploy/bootstrap-new-vps.sh +12 -12
- package/deploy/bootstrap-vps.sh +6 -3
- package/deploy/deploy-landing.sh +59 -2
- package/dist/activityLog.d.ts +49 -0
- package/dist/activityLog.js +78 -0
- package/dist/activityLog.js.map +1 -1
- package/dist/approvalHttp.d.ts +25 -0
- package/dist/approvalHttp.js +74 -18
- package/dist/approvalHttp.js.map +1 -1
- package/dist/approvalInsights.d.ts +49 -0
- package/dist/approvalInsights.js +97 -0
- package/dist/approvalInsights.js.map +1 -0
- package/dist/approvalQueue.d.ts +11 -0
- package/dist/approvalQueue.js +80 -1
- package/dist/approvalQueue.js.map +1 -1
- package/dist/approvalSignals.d.ts +124 -0
- package/dist/approvalSignals.js +512 -0
- package/dist/approvalSignals.js.map +1 -0
- package/dist/automation.d.ts +37 -0
- package/dist/automation.js +105 -61
- package/dist/automation.js.map +1 -1
- package/dist/automationSuggestions.d.ts +79 -0
- package/dist/automationSuggestions.js +150 -0
- package/dist/automationSuggestions.js.map +1 -0
- package/dist/bridge.js +78 -1
- package/dist/bridge.js.map +1 -1
- package/dist/ccPermissions.d.ts +15 -0
- package/dist/ccPermissions.js +15 -0
- package/dist/ccPermissions.js.map +1 -1
- package/dist/claudeDriver.js +74 -16
- package/dist/claudeDriver.js.map +1 -1
- package/dist/commands/patchworkInit.d.ts +8 -0
- package/dist/commands/patchworkInit.js +41 -5
- package/dist/commands/patchworkInit.js.map +1 -1
- package/dist/commands/recipe.d.ts +20 -0
- package/dist/commands/recipe.js +212 -6
- package/dist/commands/recipe.js.map +1 -1
- package/dist/commands/recipeInstall.d.ts +79 -1
- package/dist/commands/recipeInstall.js +333 -16
- package/dist/commands/recipeInstall.js.map +1 -1
- package/dist/commands/tracesExport.d.ts +83 -0
- package/dist/commands/tracesExport.js +269 -0
- package/dist/commands/tracesExport.js.map +1 -0
- package/dist/commands/tracesImport.d.ts +56 -0
- package/dist/commands/tracesImport.js +161 -0
- package/dist/commands/tracesImport.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +9 -1
- package/dist/config.js.map +1 -1
- package/dist/connectorRoutes.d.ts +43 -0
- package/dist/connectorRoutes.js +1023 -0
- package/dist/connectorRoutes.js.map +1 -0
- package/dist/connectors/asana.d.ts +198 -0
- package/dist/connectors/asana.js +679 -0
- package/dist/connectors/asana.js.map +1 -0
- package/dist/connectors/baseConnector.d.ts +36 -0
- package/dist/connectors/baseConnector.js +151 -28
- package/dist/connectors/baseConnector.js.map +1 -1
- package/dist/connectors/discord.d.ts +150 -0
- package/dist/connectors/discord.js +543 -0
- package/dist/connectors/discord.js.map +1 -0
- package/dist/connectors/github.js +11 -4
- package/dist/connectors/github.js.map +1 -1
- package/dist/connectors/gitlab.d.ts +180 -0
- package/dist/connectors/gitlab.js +582 -0
- package/dist/connectors/gitlab.js.map +1 -0
- package/dist/connectors/gmail.js +50 -10
- package/dist/connectors/gmail.js.map +1 -1
- package/dist/connectors/googleCalendar.js +36 -10
- package/dist/connectors/googleCalendar.js.map +1 -1
- package/dist/connectors/googleDrive.d.ts +34 -0
- package/dist/connectors/googleDrive.js +321 -0
- package/dist/connectors/googleDrive.js.map +1 -0
- package/dist/connectors/linear.js +23 -4
- package/dist/connectors/linear.js.map +1 -1
- package/dist/connectors/mcpOAuth.js +26 -2
- package/dist/connectors/mcpOAuth.js.map +1 -1
- package/dist/connectors/oauthStateStore.d.ts +31 -0
- package/dist/connectors/oauthStateStore.js +52 -0
- package/dist/connectors/oauthStateStore.js.map +1 -0
- package/dist/connectors/pagerduty.d.ts +160 -0
- package/dist/connectors/pagerduty.js +464 -0
- package/dist/connectors/pagerduty.js.map +1 -0
- package/dist/connectors/slack.d.ts +16 -1
- package/dist/connectors/slack.js +57 -5
- package/dist/connectors/slack.js.map +1 -1
- package/dist/connectors/tokenStorage.js +27 -2
- package/dist/connectors/tokenStorage.js.map +1 -1
- package/dist/connectors/zendesk.js +19 -1
- package/dist/connectors/zendesk.js.map +1 -1
- package/dist/cors.d.ts +10 -0
- package/dist/cors.js +29 -0
- package/dist/cors.js.map +1 -0
- package/dist/decisionReplay.d.ts +72 -0
- package/dist/decisionReplay.js +92 -0
- package/dist/decisionReplay.js.map +1 -0
- package/dist/decisionTraceLog.d.ts +6 -0
- package/dist/decisionTraceLog.js +54 -2
- package/dist/decisionTraceLog.js.map +1 -1
- package/dist/featureFlags.d.ts +17 -11
- package/dist/featureFlags.js +52 -47
- package/dist/featureFlags.js.map +1 -1
- package/dist/fp/automationInterpreter.js +25 -21
- package/dist/fp/automationInterpreter.js.map +1 -1
- package/dist/fp/automationState.js +4 -1
- package/dist/fp/automationState.js.map +1 -1
- package/dist/fp/policyParser.js +4 -1
- package/dist/fp/policyParser.js.map +1 -1
- package/dist/inboxRoutes.d.ts +22 -0
- package/dist/inboxRoutes.js +114 -0
- package/dist/inboxRoutes.js.map +1 -0
- package/dist/index.js +734 -144
- package/dist/index.js.map +1 -1
- package/dist/mcpRoutes.d.ts +37 -0
- package/dist/mcpRoutes.js +76 -0
- package/dist/mcpRoutes.js.map +1 -0
- package/dist/oauth.d.ts +3 -0
- package/dist/oauth.js +151 -26
- package/dist/oauth.js.map +1 -1
- package/dist/oauthRoutes.d.ts +32 -0
- package/dist/oauthRoutes.js +124 -0
- package/dist/oauthRoutes.js.map +1 -0
- package/dist/orchestrator/orchestratorBridge.js +2 -2
- package/dist/orchestrator/orchestratorBridge.js.map +1 -1
- package/dist/patchworkConfig.d.ts +7 -0
- package/dist/patchworkConfig.js.map +1 -1
- package/dist/pluginLoader.d.ts +12 -0
- package/dist/pluginLoader.js +43 -4
- package/dist/pluginLoader.js.map +1 -1
- package/dist/pluginWatcher.js +8 -3
- package/dist/pluginWatcher.js.map +1 -1
- package/dist/preToolUseHook.d.ts +12 -0
- package/dist/preToolUseHook.js +23 -0
- package/dist/preToolUseHook.js.map +1 -1
- package/dist/recipeOrchestration.d.ts +8 -0
- package/dist/recipeOrchestration.js +320 -39
- package/dist/recipeOrchestration.js.map +1 -1
- package/dist/recipeRoutes.d.ts +154 -0
- package/dist/recipeRoutes.js +1098 -0
- package/dist/recipeRoutes.js.map +1 -0
- package/dist/recipes/captureForRunlog.d.ts +27 -0
- package/dist/recipes/captureForRunlog.js +128 -0
- package/dist/recipes/captureForRunlog.js.map +1 -0
- package/dist/recipes/chainedRunner.d.ts +54 -3
- package/dist/recipes/chainedRunner.js +256 -36
- package/dist/recipes/chainedRunner.js.map +1 -1
- package/dist/recipes/compiler.js +3 -3
- package/dist/recipes/compiler.js.map +1 -1
- package/dist/recipes/detectSilentFail.d.ts +34 -0
- package/dist/recipes/detectSilentFail.js +105 -0
- package/dist/recipes/detectSilentFail.js.map +1 -0
- package/dist/recipes/installer.js +3 -3
- package/dist/recipes/installer.js.map +1 -1
- package/dist/recipes/manifest.js +21 -6
- package/dist/recipes/manifest.js.map +1 -1
- package/dist/recipes/migrationWarnings.d.ts +12 -0
- package/dist/recipes/migrationWarnings.js +44 -0
- package/dist/recipes/migrationWarnings.js.map +1 -0
- package/dist/recipes/replayRun.d.ts +62 -0
- package/dist/recipes/replayRun.js +97 -0
- package/dist/recipes/replayRun.js.map +1 -0
- package/dist/recipes/resolveRecipePath.d.ts +69 -0
- package/dist/recipes/resolveRecipePath.js +202 -0
- package/dist/recipes/resolveRecipePath.js.map +1 -0
- package/dist/recipes/scheduler.js +102 -11
- package/dist/recipes/scheduler.js.map +1 -1
- package/dist/recipes/schemaGenerator.js +3 -3
- package/dist/recipes/schemaGenerator.js.map +1 -1
- package/dist/recipes/toolRegistry.d.ts +5 -0
- package/dist/recipes/toolRegistry.js +9 -0
- package/dist/recipes/toolRegistry.js.map +1 -1
- package/dist/recipes/tools/asana.d.ts +16 -0
- package/dist/recipes/tools/asana.js +524 -0
- package/dist/recipes/tools/asana.js.map +1 -0
- package/dist/recipes/tools/discord.d.ts +18 -0
- package/dist/recipes/tools/discord.js +254 -0
- package/dist/recipes/tools/discord.js.map +1 -0
- package/dist/recipes/tools/file.d.ts +6 -0
- package/dist/recipes/tools/file.js +12 -8
- package/dist/recipes/tools/file.js.map +1 -1
- package/dist/recipes/tools/github.js +29 -4
- package/dist/recipes/tools/github.js.map +1 -1
- package/dist/recipes/tools/gitlab.d.ts +11 -0
- package/dist/recipes/tools/gitlab.js +285 -0
- package/dist/recipes/tools/gitlab.js.map +1 -0
- package/dist/recipes/tools/gmail.d.ts +1 -1
- package/dist/recipes/tools/gmail.js +230 -6
- package/dist/recipes/tools/gmail.js.map +1 -1
- package/dist/recipes/tools/googleDrive.d.ts +1 -0
- package/dist/recipes/tools/googleDrive.js +55 -0
- package/dist/recipes/tools/googleDrive.js.map +1 -0
- package/dist/recipes/tools/index.d.ts +8 -0
- package/dist/recipes/tools/index.js +8 -0
- package/dist/recipes/tools/index.js.map +1 -1
- package/dist/recipes/tools/jira.d.ts +14 -0
- package/dist/recipes/tools/jira.js +369 -0
- package/dist/recipes/tools/jira.js.map +1 -0
- package/dist/recipes/tools/linear.d.ts +2 -1
- package/dist/recipes/tools/linear.js +227 -3
- package/dist/recipes/tools/linear.js.map +1 -1
- package/dist/recipes/tools/meetingNotes.d.ts +21 -0
- package/dist/recipes/tools/meetingNotes.js +701 -0
- package/dist/recipes/tools/meetingNotes.js.map +1 -0
- package/dist/recipes/tools/pagerduty.d.ts +15 -0
- package/dist/recipes/tools/pagerduty.js +451 -0
- package/dist/recipes/tools/pagerduty.js.map +1 -0
- package/dist/recipes/tools/sentry.d.ts +12 -0
- package/dist/recipes/tools/sentry.js +73 -0
- package/dist/recipes/tools/sentry.js.map +1 -0
- package/dist/recipes/tools/slack.js +15 -5
- package/dist/recipes/tools/slack.js.map +1 -1
- package/dist/recipes/validation.js +83 -14
- package/dist/recipes/validation.js.map +1 -1
- package/dist/recipes/yamlRunner.d.ts +30 -2
- package/dist/recipes/yamlRunner.js +369 -70
- package/dist/recipes/yamlRunner.js.map +1 -1
- package/dist/recipesHttp.d.ts +76 -1
- package/dist/recipesHttp.js +474 -12
- package/dist/recipesHttp.js.map +1 -1
- package/dist/runLog.d.ts +78 -2
- package/dist/runLog.js +204 -6
- package/dist/runLog.js.map +1 -1
- package/dist/schemas/dry-run-plan.v1.json +139 -0
- package/dist/schemas/recipe.v1.json +684 -0
- package/dist/server.d.ts +79 -10
- package/dist/server.js +366 -1384
- package/dist/server.js.map +1 -1
- package/dist/ssrfGuard.d.ts +54 -0
- package/dist/ssrfGuard.js +122 -0
- package/dist/ssrfGuard.js.map +1 -0
- package/dist/streamableHttp.d.ts +39 -1
- package/dist/streamableHttp.js +126 -17
- package/dist/streamableHttp.js.map +1 -1
- package/dist/tools/getDocumentSymbols.d.ts +24 -0
- package/dist/tools/getDocumentSymbols.js +74 -8
- package/dist/tools/getDocumentSymbols.js.map +1 -1
- package/dist/tools/getSecurityAdvisories.js +10 -1
- package/dist/tools/getSecurityAdvisories.js.map +1 -1
- package/dist/tools/getSessionUsage.d.ts +3 -0
- package/dist/tools/getSessionUsage.js +3 -0
- package/dist/tools/getSessionUsage.js.map +1 -1
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.js +32 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/slackPostMessage.js +1 -1
- package/dist/tools/slackPostMessage.js.map +1 -1
- package/dist/tools/transaction.d.ts +19 -0
- package/dist/tools/transaction.js +29 -0
- package/dist/tools/transaction.js.map +1 -1
- package/dist/traceEncryption.d.ts +46 -0
- package/dist/traceEncryption.js +124 -0
- package/dist/traceEncryption.js.map +1 -0
- package/dist/transport.d.ts +39 -0
- package/dist/transport.js +88 -8
- package/dist/transport.js.map +1 -1
- package/package.json +22 -5
- package/templates/policies/README.md +72 -0
- package/templates/policies/conservative.json +14 -0
- package/templates/policies/developer.json +14 -0
- package/templates/policies/headless-ci.json +24 -0
- package/templates/policies/personal-assistant.json +15 -0
- package/templates/policies/regulated-industry.json +18 -0
- package/templates/recipes/project-health-check.yaml +1 -1
- package/templates/recipes/webhook/README.md +70 -0
- package/templates/recipes/webhook/capture-thought.yaml +26 -0
- package/templates/recipes/webhook/customer-escalation.yaml +49 -0
- package/templates/recipes/webhook/incident-intake.yaml +46 -0
- package/templates/recipes/webhook/meeting-prep.yaml +48 -0
- package/templates/recipes/webhook/morning-brief.yaml +57 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* detectSilentFail — recognize tool-output strings that indicate a tool
|
|
3
|
+
* silently failed but reported "success" to the runner.
|
|
4
|
+
*
|
|
5
|
+
* Background: yamlRunner originally only flagged a step as `error` when
|
|
6
|
+
* the tool's JSON return had `ok: false`. Tools returning string
|
|
7
|
+
* placeholders (`(git branches unavailable)`, `[agent step skipped:
|
|
8
|
+
* ANTHROPIC_API_KEY not set]`) succeeded as far as the runner was
|
|
9
|
+
* concerned — the failure was silent until a downstream agent
|
|
10
|
+
* regurgitated "data unavailable" in its output. The post-merge dogfood
|
|
11
|
+
* (`branch-health` recipe via Playwright) caught two distinct bugs of
|
|
12
|
+
* this class:
|
|
13
|
+
* 1. `git.stale_branches` was using an invalid `git branch --since=`
|
|
14
|
+
* flag, ALWAYS returning `(git branches unavailable)` (PR #70).
|
|
15
|
+
* 2. `agentExecutor` returns `[agent step skipped: ANTHROPIC_API_KEY
|
|
16
|
+
* not set]` when the API key is absent — the recipe completes
|
|
17
|
+
* with `status:ok` and that string written to disk.
|
|
18
|
+
*
|
|
19
|
+
* This module gives the runner a way to detect those patterns and flag
|
|
20
|
+
* the step as `error`. Default-on; recipes can opt out per-step via
|
|
21
|
+
* `silentFailDetection: false`.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Patterns that indicate a tool silently failed.
|
|
25
|
+
*
|
|
26
|
+
* The patterns are intentionally narrow — string-typed tool outputs are
|
|
27
|
+
* a rich surface and we don't want false positives. Each pattern
|
|
28
|
+
* corresponds to a known antipattern caught in the wild; bare prose is
|
|
29
|
+
* NOT flagged.
|
|
30
|
+
*/
|
|
31
|
+
const PATTERNS = [
|
|
32
|
+
// Placeholder strings emitted by `defaultGitLogSince`,
|
|
33
|
+
// `defaultGitStaleBranches` (pre-PR-70), and similar tools that
|
|
34
|
+
// catch all errors and return a parens-wrapped "unavailable" string.
|
|
35
|
+
// Match: anywhere on a single line, parens around a phrase containing
|
|
36
|
+
// "unavailable" / "not available" / "not configured" / "error" /
|
|
37
|
+
// "failed" — any of those wrapped in parens at the start of the line
|
|
38
|
+
// is a strong signal.
|
|
39
|
+
{
|
|
40
|
+
regex: /^\s*\(([^()]*?)(unavailable|not available|not configured|no data|error|failed)\)/i,
|
|
41
|
+
reason: "tool returned a parens-wrapped placeholder",
|
|
42
|
+
},
|
|
43
|
+
// Agent-step short-circuit: agentExecutor's own error/skip strings.
|
|
44
|
+
// Used by `executeAgent` when an API key is missing or the LLM
|
|
45
|
+
// returns nothing. Not surfaced as JSON, so the runner never saw it.
|
|
46
|
+
{
|
|
47
|
+
regex: /^\s*\[agent step (skipped|failed):/i,
|
|
48
|
+
reason: "agent step skipped or failed (string placeholder)",
|
|
49
|
+
},
|
|
50
|
+
// Generic step-skipped marker in case more callers adopt it.
|
|
51
|
+
{
|
|
52
|
+
regex: /^\s*\[step (skipped|failed):/i,
|
|
53
|
+
reason: "step skipped or failed (string placeholder)",
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
/**
|
|
57
|
+
* Returns a `SilentFailMatch` if `result` looks like a silent-fail
|
|
58
|
+
* placeholder, else `null`. JSON `{ok:false}` detection stays in the
|
|
59
|
+
* runner — this module only handles the string + JSON-shape patterns
|
|
60
|
+
* the runner doesn't already catch.
|
|
61
|
+
*/
|
|
62
|
+
export function detectSilentFail(result) {
|
|
63
|
+
if (result === null || result === undefined)
|
|
64
|
+
return null;
|
|
65
|
+
if (typeof result === "string") {
|
|
66
|
+
for (const { regex, reason } of PATTERNS) {
|
|
67
|
+
const m = regex.exec(result);
|
|
68
|
+
if (m) {
|
|
69
|
+
// Cap the matched fragment so error messages stay readable.
|
|
70
|
+
const matched = m[0].slice(0, 120);
|
|
71
|
+
return { reason, matched };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// String result that LOOKS like JSON — try parsing and recursing.
|
|
75
|
+
if (result.startsWith("{") || result.startsWith("[")) {
|
|
76
|
+
try {
|
|
77
|
+
return detectSilentFail(JSON.parse(result));
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
if (typeof result === "object" && !Array.isArray(result)) {
|
|
86
|
+
const obj = result;
|
|
87
|
+
// List-tool antipattern: `{count: 0, error: "..."}`. Tools that
|
|
88
|
+
// catch errors and return an empty list with an `error` field
|
|
89
|
+
// succeed-with-zero from the runner's view. Specifically targets
|
|
90
|
+
// `github.listIssues`, `linear.listIssues`, etc. flagged in the
|
|
91
|
+
// tool audit.
|
|
92
|
+
if (typeof obj.error === "string" &&
|
|
93
|
+
obj.error.length > 0 &&
|
|
94
|
+
(obj.count === 0 ||
|
|
95
|
+
(Array.isArray(obj.items) && obj.items.length === 0) ||
|
|
96
|
+
(Array.isArray(obj.results) && obj.results.length === 0))) {
|
|
97
|
+
return {
|
|
98
|
+
reason: "list-tool returned empty with error field",
|
|
99
|
+
matched: obj.error.slice(0, 120),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=detectSilentFail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detectSilentFail.js","sourceRoot":"","sources":["../../src/recipes/detectSilentFail.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAQH;;;;;;;GAOG;AACH,MAAM,QAAQ,GAA6C;IACzD,uDAAuD;IACvD,gEAAgE;IAChE,qEAAqE;IACrE,sEAAsE;IACtE,iEAAiE;IACjE,qEAAqE;IACrE,sBAAsB;IACtB;QACE,KAAK,EACH,mFAAmF;QACrF,MAAM,EAAE,4CAA4C;KACrD;IACD,oEAAoE;IACpE,+DAA+D;IAC/D,qEAAqE;IACrE;QACE,KAAK,EAAE,qCAAqC;QAC5C,MAAM,EAAE,mDAAmD;KAC5D;IACD,6DAA6D;IAC7D;QACE,KAAK,EAAE,+BAA+B;QACtC,MAAM,EAAE,6CAA6C;KACtD;CACF,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAe;IAC9C,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAEzD,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,KAAK,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;YACzC,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC7B,IAAI,CAAC,EAAE,CAAC;gBACN,4DAA4D;gBAC5D,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;gBACnC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;YAC7B,CAAC;QACH,CAAC;QACD,kEAAkE;QAClE,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACrD,IAAI,CAAC;gBACH,OAAO,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;YAC9C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACzD,MAAM,GAAG,GAAG,MAAiC,CAAC;QAC9C,gEAAgE;QAChE,8DAA8D;QAC9D,iEAAiE;QACjE,gEAAgE;QAChE,cAAc;QACd,IACE,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ;YAC7B,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;YACpB,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC;gBACd,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC;gBACpD,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,EAC3D,CAAC;YACD,OAAO;gBACL,MAAM,EAAE,2CAA2C;gBACnD,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;aACjC,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -48,10 +48,10 @@ export function installRecipeFromFile(sourcePath, opts) {
|
|
|
48
48
|
// file doesn't exist — creating
|
|
49
49
|
}
|
|
50
50
|
writeFile(destPath, JSON.stringify(recipe, null, 2));
|
|
51
|
-
//
|
|
52
|
-
|
|
51
|
+
// Permissions sidecar (`<name>.permissions.json`) was decorative — never read by toolRegistry.
|
|
52
|
+
// Removed in alpha.36 per recipe-dogfood-2026-05-01/PLAN-MASTER-V2.md A-PR4.
|
|
53
|
+
// Canonical permissions location: ~/.claude/settings.json.
|
|
53
54
|
const permissionsJson = JSON.stringify({ permissions: compiled.suggestedPermissions }, null, 2);
|
|
54
|
-
writeFile(permsPath, permissionsJson);
|
|
55
55
|
return {
|
|
56
56
|
compiled,
|
|
57
57
|
installedPath: destPath,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installer.js","sourceRoot":"","sources":["../../src/recipes/installer.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"installer.js","sourceRoot":"","sources":["../../src/recipes/installer.ts"],"names":[],"mappings":"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"}
|
package/dist/recipes/manifest.js
CHANGED
|
@@ -10,8 +10,23 @@ import path from "node:path";
|
|
|
10
10
|
const NAME_RE = /^(@[a-z0-9-]+\/)?[a-z0-9][a-z0-9\-_.]*$/;
|
|
11
11
|
const VERSION_RE = /^\d+\.\d+\.\d+/;
|
|
12
12
|
const YAML_EXT_RE = /\.ya?ml$/;
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Recipe filenames in the manifest must be plain basenames, not paths.
|
|
15
|
+
* Rejects "../escape.yaml", "subdir/foo.yaml", "/etc/passwd.yaml", control
|
|
16
|
+
* characters, and so on — anything that could resolve outside the install
|
|
17
|
+
* directory when the consumer does `path.join(installDir, recipes.main)`.
|
|
18
|
+
*/
|
|
19
|
+
function isSafeRecipeBasename(filename) {
|
|
20
|
+
if (typeof filename !== "string" || filename.length === 0)
|
|
21
|
+
return false;
|
|
22
|
+
if (filename === "." || filename === "..")
|
|
23
|
+
return false;
|
|
24
|
+
if (filename.includes("/") || filename.includes("\\"))
|
|
25
|
+
return false;
|
|
26
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: explicit control-char check
|
|
27
|
+
if (/[\x00-\x1F\x7F]/.test(filename))
|
|
28
|
+
return false;
|
|
29
|
+
return YAML_EXT_RE.test(filename);
|
|
15
30
|
}
|
|
16
31
|
/**
|
|
17
32
|
* Validate an unknown value as a RecipeManifest.
|
|
@@ -45,16 +60,16 @@ export function validateManifest(manifest) {
|
|
|
45
60
|
throw new Error('recipe.json: "recipes" is required and must be an object');
|
|
46
61
|
}
|
|
47
62
|
const recipes = m.recipes;
|
|
48
|
-
if (!
|
|
49
|
-
throw new Error(`recipe.json: "recipes.main" must be a .yaml or .yml
|
|
63
|
+
if (!isSafeRecipeBasename(recipes.main)) {
|
|
64
|
+
throw new Error(`recipe.json: "recipes.main" must be a .yaml or .yml basename without path separators or control characters — got "${recipes.main}"`);
|
|
50
65
|
}
|
|
51
66
|
if (recipes.children !== undefined) {
|
|
52
67
|
if (!Array.isArray(recipes.children)) {
|
|
53
68
|
throw new Error('recipe.json: "recipes.children" must be an array');
|
|
54
69
|
}
|
|
55
70
|
for (let i = 0; i < recipes.children.length; i++) {
|
|
56
|
-
if (!
|
|
57
|
-
throw new Error(`recipe.json: "recipes.children[${i}]" must be a .yaml or .yml
|
|
71
|
+
if (!isSafeRecipeBasename(recipes.children[i])) {
|
|
72
|
+
throw new Error(`recipe.json: "recipes.children[${i}]" must be a .yaml or .yml basename without path separators or control characters — got "${recipes.children[i]}"`);
|
|
58
73
|
}
|
|
59
74
|
}
|
|
60
75
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/recipes/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,IAAI,MAAM,WAAW,CAAC;AA0B7B,MAAM,OAAO,GAAG,yCAAyC,CAAC;AAC1D,MAAM,UAAU,GAAG,gBAAgB,CAAC;AACpC,MAAM,WAAW,GAAG,UAAU,CAAC;AAE/B,SAAS,
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/recipes/manifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface PermissionsSidecarWarningResult {
|
|
2
|
+
count: number;
|
|
3
|
+
warned: boolean;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Scans `recipesDir` for legacy permissions sidecar files. Emits a single
|
|
7
|
+
* `console.warn` if any are found (skipped in test env). Returns the count
|
|
8
|
+
* for caller observability + tests.
|
|
9
|
+
*/
|
|
10
|
+
export declare function warnAboutLegacyPermissionsSidecars(recipesDir: string, options?: {
|
|
11
|
+
warn?: (msg: string) => void;
|
|
12
|
+
}): PermissionsSidecarWarningResult;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readdirSync } from "node:fs";
|
|
2
|
+
/**
|
|
3
|
+
* Boot-time scan for legacy `<name>.permissions.json` sidecars left over from
|
|
4
|
+
* pre-alpha.36 installs. The sidecar files were decorative — never read by
|
|
5
|
+
* toolRegistry — so they're safe to ignore on disk. Emits a single warning
|
|
6
|
+
* pointing users at the canonical permission location (~/.claude/settings.json).
|
|
7
|
+
*
|
|
8
|
+
* Per recipe-dogfood-2026-05-01/PLAN-MASTER-V2.md A-PR4 §6 (R2 L-2):
|
|
9
|
+
* - Fires ONCE per boot, not per recipe.
|
|
10
|
+
* - Skipped under NODE_ENV=test so vitest output stays clean.
|
|
11
|
+
* - Migration script for users who want to archive:
|
|
12
|
+
* find ~/.patchwork/recipes -name '*.permissions.json' \
|
|
13
|
+
* -exec mv {} ~/.patchwork/recipes/.permissions-archive/ \;
|
|
14
|
+
*/
|
|
15
|
+
const DOC_URL = "https://github.com/Oolab-labs/patchwork-os/blob/main/docs/dogfood/recipe-dogfood-2026-05-01/PLAN-MASTER-V2.md";
|
|
16
|
+
/**
|
|
17
|
+
* Scans `recipesDir` for legacy permissions sidecar files. Emits a single
|
|
18
|
+
* `console.warn` if any are found (skipped in test env). Returns the count
|
|
19
|
+
* for caller observability + tests.
|
|
20
|
+
*/
|
|
21
|
+
export function warnAboutLegacyPermissionsSidecars(recipesDir, options = {}) {
|
|
22
|
+
let entries;
|
|
23
|
+
try {
|
|
24
|
+
entries = readdirSync(recipesDir);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return { count: 0, warned: false };
|
|
28
|
+
}
|
|
29
|
+
const sidecars = entries.filter((f) => f.endsWith(".permissions.json"));
|
|
30
|
+
const count = sidecars.length;
|
|
31
|
+
if (count === 0) {
|
|
32
|
+
return { count: 0, warned: false };
|
|
33
|
+
}
|
|
34
|
+
if (process.env.NODE_ENV === "test" && !options.warn) {
|
|
35
|
+
return { count, warned: false };
|
|
36
|
+
}
|
|
37
|
+
const warn = options.warn ?? ((msg) => console.warn(msg));
|
|
38
|
+
warn(`[patchwork] Found ${count} legacy <name>.permissions.json sidecar file${count === 1 ? "" : "s"} in ${recipesDir}. ` +
|
|
39
|
+
`These are decorative and no longer generated (alpha.36+). ` +
|
|
40
|
+
`Configure tool gating in ~/.claude/settings.json. ` +
|
|
41
|
+
`Migration guide: ${DOC_URL}`);
|
|
42
|
+
return { count, warned: true };
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=migrationWarnings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrationWarnings.js","sourceRoot":"","sources":["../../src/recipes/migrationWarnings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC;;;;;;;;;;;;GAYG;AAEH,MAAM,OAAO,GACX,+GAA+G,CAAC;AAOlH;;;;GAIG;AACH,MAAM,UAAU,kCAAkC,CAChD,UAAkB,EAClB,UAA4C,EAAE;IAE9C,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACrC,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAAC;IACxE,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC;IAE9B,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;QAChB,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACrC,CAAC;IAED,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QACrD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAClC,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAClE,IAAI,CACF,qBAAqB,KAAK,+CACxB,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GACrB,OAAO,UAAU,IAAI;QACnB,4DAA4D;QAC5D,oDAAoD;QACpD,oBAAoB,OAAO,EAAE,CAChC,CAAC;IACF,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AACjC,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* replayRun — VD-4 mocked replay entrypoint.
|
|
3
|
+
*
|
|
4
|
+
* Given an original `RecipeRun` (looked up from `RecipeRunLog`), build a
|
|
5
|
+
* `mockedOutputs` map from each step's captured `output` (VD-2) and
|
|
6
|
+
* re-run the recipe through `runChainedRecipe` with all tool/agent
|
|
7
|
+
* execution short-circuited to those captured values.
|
|
8
|
+
*
|
|
9
|
+
* Pure mocked-only: no external network calls, no write side effects.
|
|
10
|
+
* The new run is logged with `triggerSource: "replay:<originalSeq>"`
|
|
11
|
+
* so the audit trail is clear.
|
|
12
|
+
*
|
|
13
|
+
* Real-mode replay (write tools really fire) is deliberately NOT in
|
|
14
|
+
* this module. It needs a confirmation UX, a kill-switch interaction,
|
|
15
|
+
* and possibly a connector-level read/write split. Ship separately
|
|
16
|
+
* after explicit user approval.
|
|
17
|
+
*/
|
|
18
|
+
import type { ActivityLog } from "../activityLog.js";
|
|
19
|
+
import type { RecipeRun, RecipeRunLog } from "../runLog.js";
|
|
20
|
+
import type { ChainedRecipe, ChainedRunResult } from "./chainedRunner.js";
|
|
21
|
+
import type { RunnerDeps } from "./yamlRunner.js";
|
|
22
|
+
export interface ReplayDeps {
|
|
23
|
+
/** Long-lived run log so the new run shows up live in the dashboard. */
|
|
24
|
+
runLog: RecipeRunLog;
|
|
25
|
+
/** Activity log for live-tail SSE on the new run. Optional. */
|
|
26
|
+
activityLog?: ActivityLog;
|
|
27
|
+
/** Workdir + claudeCodeFn etc., reused from the orchestrator. */
|
|
28
|
+
runnerDeps: RunnerDeps;
|
|
29
|
+
}
|
|
30
|
+
export interface ReplayResult {
|
|
31
|
+
ok: boolean;
|
|
32
|
+
/** New run's seq if the replay started successfully. */
|
|
33
|
+
newSeq?: number;
|
|
34
|
+
/** Underlying recipe-run result for callers that want full detail. */
|
|
35
|
+
result?: ChainedRunResult;
|
|
36
|
+
error?: string;
|
|
37
|
+
/** Steps that lacked captured outputs and were dropped from the
|
|
38
|
+
* mocked map. The replay still runs but those steps fall through to
|
|
39
|
+
* REAL execution — callers may want to surface this as a warning. */
|
|
40
|
+
unmockedSteps?: string[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build the `mockedOutputs` map. Truncated captures (>8 KB envelope from
|
|
44
|
+
* VD-2's `captureForRunlog`) are excluded — replaying with a `[truncated]`
|
|
45
|
+
* preview would be misleading. Steps without captures are excluded too.
|
|
46
|
+
*/
|
|
47
|
+
export declare function buildMockedOutputs(originalRun: RecipeRun): {
|
|
48
|
+
outputs: Map<string, unknown>;
|
|
49
|
+
unmocked: string[];
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Fire a mocked replay of `originalRun` against `recipe`. The recipe
|
|
53
|
+
* argument is supplied by the caller (typically loaded fresh from disk
|
|
54
|
+
* by name) so an EDITED recipe can be replayed against captured
|
|
55
|
+
* outputs — that's the debugging value of replay.
|
|
56
|
+
*/
|
|
57
|
+
export declare function replayMockedRun(opts: {
|
|
58
|
+
originalRun: RecipeRun;
|
|
59
|
+
recipe: ChainedRecipe;
|
|
60
|
+
sourcePath?: string;
|
|
61
|
+
deps: ReplayDeps;
|
|
62
|
+
}): Promise<ReplayResult>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* replayRun — VD-4 mocked replay entrypoint.
|
|
3
|
+
*
|
|
4
|
+
* Given an original `RecipeRun` (looked up from `RecipeRunLog`), build a
|
|
5
|
+
* `mockedOutputs` map from each step's captured `output` (VD-2) and
|
|
6
|
+
* re-run the recipe through `runChainedRecipe` with all tool/agent
|
|
7
|
+
* execution short-circuited to those captured values.
|
|
8
|
+
*
|
|
9
|
+
* Pure mocked-only: no external network calls, no write side effects.
|
|
10
|
+
* The new run is logged with `triggerSource: "replay:<originalSeq>"`
|
|
11
|
+
* so the audit trail is clear.
|
|
12
|
+
*
|
|
13
|
+
* Real-mode replay (write tools really fire) is deliberately NOT in
|
|
14
|
+
* this module. It needs a confirmation UX, a kill-switch interaction,
|
|
15
|
+
* and possibly a connector-level read/write split. Ship separately
|
|
16
|
+
* after explicit user approval.
|
|
17
|
+
*/
|
|
18
|
+
import { runChainedRecipe } from "./chainedRunner.js";
|
|
19
|
+
import { buildChainedDeps } from "./yamlRunner.js";
|
|
20
|
+
/**
|
|
21
|
+
* Build the `mockedOutputs` map. Truncated captures (>8 KB envelope from
|
|
22
|
+
* VD-2's `captureForRunlog`) are excluded — replaying with a `[truncated]`
|
|
23
|
+
* preview would be misleading. Steps without captures are excluded too.
|
|
24
|
+
*/
|
|
25
|
+
export function buildMockedOutputs(originalRun) {
|
|
26
|
+
const outputs = new Map();
|
|
27
|
+
const unmocked = [];
|
|
28
|
+
for (const step of originalRun.stepResults ?? []) {
|
|
29
|
+
if (step.status === "skipped")
|
|
30
|
+
continue;
|
|
31
|
+
const out = step.output;
|
|
32
|
+
if (out === undefined) {
|
|
33
|
+
unmocked.push(step.id);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
// Skip the truncation envelope — replaying with a preview slice
|
|
37
|
+
// would be misleading.
|
|
38
|
+
if (out !== null &&
|
|
39
|
+
typeof out === "object" &&
|
|
40
|
+
out["[truncated]"] === true) {
|
|
41
|
+
unmocked.push(step.id);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
outputs.set(step.id, out);
|
|
45
|
+
}
|
|
46
|
+
return { outputs, unmocked };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Fire a mocked replay of `originalRun` against `recipe`. The recipe
|
|
50
|
+
* argument is supplied by the caller (typically loaded fresh from disk
|
|
51
|
+
* by name) so an EDITED recipe can be replayed against captured
|
|
52
|
+
* outputs — that's the debugging value of replay.
|
|
53
|
+
*/
|
|
54
|
+
export async function replayMockedRun(opts) {
|
|
55
|
+
const { originalRun, recipe, sourcePath, deps } = opts;
|
|
56
|
+
const { outputs, unmocked } = buildMockedOutputs(originalRun);
|
|
57
|
+
const chainedDeps = buildChainedDeps(deps.runnerDeps, deps.runnerDeps.claudeCodeFn ??
|
|
58
|
+
(async () => {
|
|
59
|
+
return "";
|
|
60
|
+
}));
|
|
61
|
+
const runOptions = {
|
|
62
|
+
env: { ...process.env },
|
|
63
|
+
maxConcurrency: recipe.maxConcurrency ?? 4,
|
|
64
|
+
maxDepth: recipe.maxDepth ?? 3,
|
|
65
|
+
dryRun: false,
|
|
66
|
+
...(sourcePath !== undefined && { sourcePath }),
|
|
67
|
+
runLog: deps.runLog,
|
|
68
|
+
...(deps.activityLog !== undefined && { activityLog: deps.activityLog }),
|
|
69
|
+
mockedOutputs: outputs,
|
|
70
|
+
// BUG-4 fix: tag the new run's taskId so it's distinguishable from a
|
|
71
|
+
// fresh run. Searchable as `taskId LIKE 'replay:<seq>:%'`.
|
|
72
|
+
taskIdPrefix: `replay:${originalRun.seq}`,
|
|
73
|
+
};
|
|
74
|
+
try {
|
|
75
|
+
const result = await runChainedRecipe(recipe, runOptions, chainedDeps);
|
|
76
|
+
// The runner's completeRun path will have already written the new
|
|
77
|
+
// run to the log. Find its seq — most-recent matching recipeName,
|
|
78
|
+
// started after originalRun.doneAt.
|
|
79
|
+
const recent = deps.runLog.query({ recipe: recipe.name, limit: 5 });
|
|
80
|
+
const newRun = recent.find((r) => r.createdAt > originalRun.doneAt);
|
|
81
|
+
return {
|
|
82
|
+
ok: result.success,
|
|
83
|
+
...(newRun?.seq !== undefined && { newSeq: newRun.seq }),
|
|
84
|
+
result,
|
|
85
|
+
...(result.errorMessage !== undefined && { error: result.errorMessage }),
|
|
86
|
+
...(unmocked.length > 0 && { unmockedSteps: unmocked }),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
return {
|
|
91
|
+
ok: false,
|
|
92
|
+
error: err instanceof Error ? err.message : String(err),
|
|
93
|
+
...(unmocked.length > 0 && { unmockedSteps: unmocked }),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=replayRun.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replayRun.js","sourceRoot":"","sources":["../../src/recipes/replayRun.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* resolveRecipePath — recipe-runner path jail.
|
|
3
|
+
*
|
|
4
|
+
* Closes G-security F-01 (CRITICAL — `file.read/write/append` accept any
|
|
5
|
+
* absolute path), F-02 (CRITICAL — template-substituted vars escape via
|
|
6
|
+
* `..`), and the R2 C-1 chained-runner third-substitution-site gap.
|
|
7
|
+
*
|
|
8
|
+
* Mirrors the symlink-walking strategy from `src/tools/utils.ts:104-200`
|
|
9
|
+
* (`resolveFilePath`) but operates against an allowlist of recipe-roots
|
|
10
|
+
* rather than a single workspace root:
|
|
11
|
+
*
|
|
12
|
+
* - `~/.patchwork/` (always allowed — recipe install dir)
|
|
13
|
+
* - the bridge / CLI workspace (always allowed — passed in via `opts.workspace`)
|
|
14
|
+
* - `os.tmpdir()` (OFF by default; opt-in via the
|
|
15
|
+
* `CLAUDE_IDE_BRIDGE_RECIPE_TMP_JAIL=1`
|
|
16
|
+
* env var, per R2 C-2 maintainer decision)
|
|
17
|
+
*
|
|
18
|
+
* On any escape (null byte, segment outside all roots, symlink target
|
|
19
|
+
* outside roots, hardlink on a write target) the helper throws an `Error`
|
|
20
|
+
* with `err.code = "recipe_path_jail_escape"`. Callers and tests must
|
|
21
|
+
* assert on `err.code`, never on message text (R2 M-4).
|
|
22
|
+
*
|
|
23
|
+
* Defense-in-depth — apply at every layer:
|
|
24
|
+
* - `src/recipes/tools/file.ts` (per-tool execute())
|
|
25
|
+
* - `src/recipes/yamlRunner.ts:976-994` (default StepDeps file ops)
|
|
26
|
+
* - `src/recipes/yamlRunner.ts:642` (post-render path snapshot)
|
|
27
|
+
* - `src/recipes/yamlRunner.ts:1252-1262` (chained-runner executeTool)
|
|
28
|
+
* - `src/recipes/chainedRunner.ts:194-205` (template-substitution site)
|
|
29
|
+
* - `src/recipeRoutes.ts:131-138 :172-181` (HTTP vars validator)
|
|
30
|
+
* - `src/commands/recipe.ts:1080-1102` (CLI warn on out-of-jail recipe ref)
|
|
31
|
+
*/
|
|
32
|
+
export type RecipePathJailError = Error & {
|
|
33
|
+
code: "recipe_path_jail_escape";
|
|
34
|
+
};
|
|
35
|
+
export interface ResolveRecipePathOptions {
|
|
36
|
+
/** True when the caller will write/append/mkdir at the resolved path (enables hardlink check). */
|
|
37
|
+
write?: boolean;
|
|
38
|
+
/** Optional workspace allowlist root. Defaults to `process.cwd()`. */
|
|
39
|
+
workspace?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Override the tmp-jail opt-in. Used by tests to assert the env-var
|
|
42
|
+
* behavior without polluting `process.env` across the suite. Production
|
|
43
|
+
* callers should leave this undefined and rely on
|
|
44
|
+
* `CLAUDE_IDE_BRIDGE_RECIPE_TMP_JAIL=1`.
|
|
45
|
+
*/
|
|
46
|
+
allowTmp?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Override the home dir. Used by tests to assert behavior without
|
|
49
|
+
* touching the real `~`. Production callers leave undefined.
|
|
50
|
+
*/
|
|
51
|
+
homeDir?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve a recipe-supplied path, expanding `~/`, normalising, and asserting
|
|
55
|
+
* the result lives inside one of the jail roots after symlink resolution.
|
|
56
|
+
*
|
|
57
|
+
* Throws `RecipePathJailError` (code `"recipe_path_jail_escape"`) on any
|
|
58
|
+
* containment violation. Callers should propagate the error unchanged so
|
|
59
|
+
* tests can assert on `err.code`.
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveRecipePath(rawPath: string, opts?: ResolveRecipePathOptions): string;
|
|
62
|
+
/**
|
|
63
|
+
* Side-effect-free predicate variant — returns `null` on jail escape rather
|
|
64
|
+
* than throwing. Used by the CLI `recipe run` warn path (F-10), which wants
|
|
65
|
+
* to write a stderr notice when a recipe **file** lives outside the jail
|
|
66
|
+
* but still loads it (the YAML loader is a separate trust boundary from the
|
|
67
|
+
* tool dispatch jail).
|
|
68
|
+
*/
|
|
69
|
+
export declare function tryResolveRecipePath(rawPath: string, opts?: ResolveRecipePathOptions): string | null;
|