@open-agent-toolkit/cli 0.3.7 → 0.3.9
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 +3 -3
- package/assets/agents/oat-codebase-mapper.md +3 -2
- package/assets/agents/oat-reviewer.md +5 -13
- package/assets/agents/skeptical-evaluator.md +6 -2
- package/assets/bundle-metadata.json +1 -1
- package/assets/docs/cli-utilities/backlog-lifecycle.md +3 -2
- package/assets/docs/cli-utilities/bootstrap.md +5 -2
- package/assets/docs/cli-utilities/config-and-local-state.md +13 -6
- package/assets/docs/cli-utilities/configuration.md +25 -10
- package/assets/docs/cli-utilities/tool-packs.md +60 -10
- package/assets/docs/cli-utilities/workflow-gates.md +37 -0
- package/assets/docs/contributing/code.md +9 -5
- package/assets/docs/provider-sync/commands.md +17 -8
- package/assets/docs/provider-sync/instruction-sync.md +241 -39
- package/assets/docs/provider-sync/providers.md +20 -0
- package/assets/docs/provider-sync/scope-and-surface.md +3 -1
- package/assets/docs/reference/cli-reference.md +4 -1
- package/assets/docs/reference/oat-directory-structure.md +12 -11
- package/assets/docs/reference/troubleshooting.md +19 -4
- package/assets/docs/workflows/projects/evidence-layers.md +5 -1
- package/assets/docs/workflows/projects/implementation-execution.md +22 -8
- package/assets/docs/workflows/projects/lifecycle.md +4 -6
- package/assets/docs/workflows/projects/retro.md +7 -0
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-agent-instructions-analyze/SKILL.md +4 -4
- package/assets/skills/oat-agent-instructions-analyze/references/analysis-artifact-template.md +5 -5
- package/assets/skills/oat-agent-instructions-analyze/references/docs/rules-files.md +14 -11
- package/assets/skills/oat-agent-instructions-analyze/references/quality-checklist.md +2 -2
- package/assets/skills/oat-agent-instructions-analyze/scripts/resolve-instruction-files.sh +4 -3
- package/assets/skills/oat-agent-instructions-analyze/scripts/resolve-providers.sh +17 -6
- package/assets/skills/oat-agent-instructions-apply/SKILL.md +3 -3
- package/assets/skills/oat-agent-instructions-apply/references/docs/rules-files.md +14 -11
- package/assets/skills/oat-dispatch-subagents/SKILL.md +1 -1
- package/assets/skills/oat-dispatch-subagents/references/managed-claude-example.json +387 -0
- package/assets/skills/oat-dispatch-subagents/references/record-schema.md +75 -0
- package/assets/skills/oat-doctor/SKILL.md +17 -13
- package/assets/skills/oat-project-autonomous/references/gate-inventory.md +1 -1
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +1 -1
- package/assets/skills/oat-project-implement/SKILL.md +2 -2
- package/assets/skills/oat-project-implement/references/completion-and-closeout.md +33 -11
- package/assets/skills/oat-project-implement/references/dispatch-and-dry-run.md +24 -22
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +1 -1
- package/assets/skills/oat-project-implement/references/phase-execution.md +109 -8
- package/assets/skills/oat-project-lite/SKILL.md +23 -1
- package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +1 -1
- package/assets/skills/oat-project-next/SKILL.md +3 -3
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +1 -1
- package/assets/skills/oat-project-progress/SKILL.md +2 -2
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +1 -1
- package/assets/skills/oat-project-retro/SKILL.md +70 -6
- package/assets/skills/oat-project-review-provide/SKILL.md +3 -1
- package/assets/skills/oat-project-review-provide-remote/SKILL.md +3 -1
- package/assets/templates/docs-app-mkdocs/docs/contributing.md +1 -3
- package/assets/templates/pjm-agents.md +7 -3
- package/assets/templates/state.md +2 -2
- package/dist/agents/canonical/resolve.d.ts +6 -4
- package/dist/agents/canonical/resolve.d.ts.map +1 -1
- package/dist/agents/canonical/resolve.js +6 -4
- package/dist/commands/backlog/archive.d.ts +10 -2
- package/dist/commands/backlog/archive.d.ts.map +1 -1
- package/dist/commands/backlog/archive.js +22 -6
- package/dist/commands/backlog/index.d.ts.map +1 -1
- package/dist/commands/backlog/index.js +5 -1
- package/dist/commands/backlog/rewrite-references.d.ts +41 -0
- package/dist/commands/backlog/rewrite-references.d.ts.map +1 -0
- package/dist/commands/backlog/rewrite-references.js +516 -0
- package/dist/commands/config/index.d.ts +1 -0
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +69 -34
- package/dist/commands/gate/index.d.ts.map +1 -1
- package/dist/commands/gate/index.js +51 -2
- package/dist/commands/gate/review-verdict.d.ts +22 -0
- package/dist/commands/gate/review-verdict.d.ts.map +1 -1
- package/dist/commands/gate/review-verdict.js +111 -0
- package/dist/commands/init/index.d.ts +6 -0
- package/dist/commands/init/index.d.ts.map +1 -1
- package/dist/commands/init/index.js +27 -4
- package/dist/commands/init/tools/index.d.ts +18 -1
- package/dist/commands/init/tools/index.d.ts.map +1 -1
- package/dist/commands/init/tools/index.js +84 -17
- package/dist/commands/init/tools/project-guidance.d.ts +19 -4
- package/dist/commands/init/tools/project-guidance.d.ts.map +1 -1
- package/dist/commands/init/tools/project-guidance.js +58 -15
- package/dist/commands/init/tools/workflows/index.d.ts.map +1 -1
- package/dist/commands/init/tools/workflows/index.js +5 -1
- package/dist/commands/instructions/instructions.types.d.ts +105 -4
- package/dist/commands/instructions/instructions.types.d.ts.map +1 -1
- package/dist/commands/instructions/instructions.types.js +4 -5
- package/dist/commands/instructions/instructions.utils.d.ts +161 -6
- package/dist/commands/instructions/instructions.utils.d.ts.map +1 -1
- package/dist/commands/instructions/instructions.utils.js +650 -12
- package/dist/commands/instructions/sync/sync.d.ts.map +1 -1
- package/dist/commands/instructions/sync/sync.js +256 -18
- package/dist/commands/instructions/validate/validate.d.ts.map +1 -1
- package/dist/commands/instructions/validate/validate.js +36 -8
- package/dist/commands/pjm/index.d.ts.map +1 -1
- package/dist/commands/pjm/index.js +19 -4
- package/dist/commands/pjm/init.d.ts +3 -0
- package/dist/commands/pjm/init.d.ts.map +1 -1
- package/dist/commands/pjm/init.js +23 -5
- package/dist/commands/project/dispatch/canonical-role.d.ts +33 -0
- package/dist/commands/project/dispatch/canonical-role.d.ts.map +1 -0
- package/dist/commands/project/dispatch/canonical-role.js +114 -0
- package/dist/commands/project/dispatch/index.d.ts.map +1 -1
- package/dist/commands/project/dispatch/index.js +3 -1
- package/dist/commands/project/dispatch/record.d.ts.map +1 -1
- package/dist/commands/project/dispatch/record.js +146 -4
- package/dist/commands/shared/agents-md.d.ts +26 -5
- package/dist/commands/shared/agents-md.d.ts.map +1 -1
- package/dist/commands/shared/agents-md.js +182 -3
- package/dist/commands/sync/apply.d.ts.map +1 -1
- package/dist/commands/sync/apply.js +28 -21
- package/dist/commands/tools/guidance/index.d.ts +15 -0
- package/dist/commands/tools/guidance/index.d.ts.map +1 -0
- package/dist/commands/tools/guidance/index.js +81 -0
- package/dist/commands/tools/index.d.ts.map +1 -1
- package/dist/commands/tools/index.js +2 -0
- package/dist/commands/tools/shared/sync-evidence.d.ts.map +1 -1
- package/dist/commands/tools/shared/sync-evidence.js +32 -5
- package/dist/config/oat-config.d.ts +57 -3
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +223 -15
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +12 -2
- package/dist/engine/compute-plan.d.ts.map +1 -1
- package/dist/engine/compute-plan.js +31 -3
- package/dist/providers/claude/dispatch-envelope.d.ts +53 -0
- package/dist/providers/claude/dispatch-envelope.d.ts.map +1 -1
- package/dist/providers/claude/dispatch-envelope.js +324 -48
- package/dist/providers/claude/rule-transform.js +1 -1
- package/dist/providers/copilot/rule-transform.js +1 -1
- package/dist/providers/cursor/rule-transform.js +1 -1
- package/dist/providers/identity/absolute-paths.d.ts +13 -0
- package/dist/providers/identity/absolute-paths.d.ts.map +1 -1
- package/dist/providers/identity/absolute-paths.js +27 -14
- package/dist/providers/identity/generic-dispatch-record.d.ts +212 -0
- package/dist/providers/identity/generic-dispatch-record.d.ts.map +1 -1
- package/dist/providers/identity/generic-dispatch-record.js +78 -19
- package/dist/providers/identity/oat-dispatch-record.d.ts +5 -0
- package/dist/providers/identity/oat-dispatch-record.d.ts.map +1 -1
- package/dist/providers/identity/oat-dispatch-record.js +11 -2
- package/dist/providers/shared/registry.d.ts +6 -0
- package/dist/providers/shared/registry.d.ts.map +1 -1
- package/dist/providers/shared/registry.js +30 -0
- package/dist/rules/canonical/parse.d.ts.map +1 -1
- package/dist/rules/canonical/parse.js +38 -3
- package/package.json +2 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { collectAbsolutePathViolations } from '../identity/absolute-paths.js';
|
|
2
|
+
import { collectSensitiveDispatchContent, genericDispatchRecordBaseSchema, identityFieldsOf, parseGenericDispatchRecord, redactSensitiveValues, refineGenericDispatchRecord, } from '../identity/generic-dispatch-record.js';
|
|
2
3
|
import YAML from 'yaml';
|
|
3
4
|
import { z } from 'zod';
|
|
4
5
|
import { readOatManagedClaudeRole } from './codec/materialize.js';
|
|
@@ -97,76 +98,166 @@ function definitionFrontmatter(content) {
|
|
|
97
98
|
}
|
|
98
99
|
return parsed;
|
|
99
100
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
/** Render a Zod issue path as `a.b[0].c`, or `<root>` when empty. */
|
|
102
|
+
export function formatIssuePath(prefix, path) {
|
|
103
|
+
let rendered = prefix ?? '';
|
|
104
|
+
for (const segment of path) {
|
|
105
|
+
rendered +=
|
|
106
|
+
typeof segment === 'number'
|
|
107
|
+
? `[${segment}]`
|
|
108
|
+
: rendered === ''
|
|
109
|
+
? segment
|
|
110
|
+
: `.${segment}`;
|
|
106
111
|
}
|
|
112
|
+
return rendered === '' ? '<root>' : rendered;
|
|
113
|
+
}
|
|
114
|
+
export function zodIssueViolations(stage, prefix, error) {
|
|
115
|
+
return error.issues.map((issue) => ({
|
|
116
|
+
stage,
|
|
117
|
+
path: formatIssuePath(prefix, issue.path),
|
|
118
|
+
message: issue.message,
|
|
119
|
+
}));
|
|
107
120
|
}
|
|
108
121
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
122
|
+
* The launch consistency checks, in the order the throwing boundary has always
|
|
123
|
+
* applied them, so its first reported error is unchanged. A check that needs
|
|
124
|
+
* an input which did not parse — the launch payload, the managed role marker,
|
|
125
|
+
* or the definition frontmatter — is skipped rather than guessed at.
|
|
112
126
|
*/
|
|
113
|
-
|
|
114
|
-
const
|
|
127
|
+
function launchConsistencyViolations(resolution, launch, definition) {
|
|
128
|
+
const violations = [];
|
|
115
129
|
const provider = resolution.providers.claude;
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
130
|
+
const selected = provider.selection.target;
|
|
131
|
+
if (provider.target.model !== selected.model ||
|
|
132
|
+
provider.target.effort !== selected.effort ||
|
|
133
|
+
JSON.stringify(provider.target.capabilityEvidence) !==
|
|
134
|
+
JSON.stringify(selected.capabilityEvidence)) {
|
|
135
|
+
violations.push({
|
|
136
|
+
path: 'resolution.providers.claude.target',
|
|
137
|
+
message: 'Claude resolver target and selection target disagree on model, effort, or capability evidence.',
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const validation = validateClaudeDispatchCapability(selected);
|
|
119
141
|
if (!validation.valid) {
|
|
120
|
-
|
|
142
|
+
violations.push({
|
|
143
|
+
path: 'resolution.providers.claude.selection.target',
|
|
144
|
+
message: validation.reason ?? 'Resolver selected an invalid Claude target.',
|
|
145
|
+
});
|
|
121
146
|
}
|
|
122
147
|
const baseRole = provider.selection.role === 'reviewer'
|
|
123
148
|
? 'oat-reviewer'
|
|
124
149
|
: 'oat-phase-implementer';
|
|
125
150
|
const expectedVariant = buildClaudeEffortVariantName({
|
|
126
151
|
agentName: baseRole,
|
|
127
|
-
model:
|
|
128
|
-
effort:
|
|
152
|
+
model: selected.model,
|
|
153
|
+
effort: selected.effort,
|
|
129
154
|
});
|
|
130
155
|
if (provider.dispatchArgs.variant !== expectedVariant) {
|
|
131
|
-
|
|
156
|
+
violations.push({
|
|
157
|
+
path: 'resolution.providers.claude.dispatchArgs.variant',
|
|
158
|
+
message: `Managed Claude resolver variant ${provider.dispatchArgs.variant} does not match selected target ${expectedVariant}.`,
|
|
159
|
+
});
|
|
132
160
|
}
|
|
133
|
-
if (launch.variant !== expectedVariant) {
|
|
134
|
-
|
|
161
|
+
if (launch !== null && launch.variant !== expectedVariant) {
|
|
162
|
+
violations.push({
|
|
163
|
+
path: 'payload.variant',
|
|
164
|
+
message: `Claude launch variant ${launch.variant} does not match resolver-selected variant ${expectedVariant}.`,
|
|
165
|
+
});
|
|
135
166
|
}
|
|
136
|
-
const managed = readOatManagedClaudeRole(
|
|
167
|
+
const managed = readOatManagedClaudeRole(definition);
|
|
137
168
|
if (!managed || managed.roleName !== expectedVariant) {
|
|
138
|
-
|
|
169
|
+
violations.push({
|
|
170
|
+
path: 'definition',
|
|
171
|
+
message: `Generated Claude definition ${expectedVariant} is absent or is not the matching OAT-managed role.`,
|
|
172
|
+
});
|
|
139
173
|
}
|
|
140
|
-
if (
|
|
141
|
-
JSON.stringify(
|
|
142
|
-
|
|
174
|
+
if (managed &&
|
|
175
|
+
JSON.stringify(managed.capabilityEvidence) !==
|
|
176
|
+
JSON.stringify(selected.capabilityEvidence)) {
|
|
177
|
+
violations.push({
|
|
178
|
+
path: 'definition',
|
|
179
|
+
message: 'Generated Claude definition capability evidence does not match the resolver-selected capability.',
|
|
180
|
+
});
|
|
143
181
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
182
|
+
let frontmatter = null;
|
|
183
|
+
try {
|
|
184
|
+
frontmatter = definitionFrontmatter(definition);
|
|
147
185
|
}
|
|
148
|
-
|
|
149
|
-
|
|
186
|
+
catch (error) {
|
|
187
|
+
violations.push({
|
|
188
|
+
path: 'definition',
|
|
189
|
+
message: error instanceof Error ? error.message : String(error),
|
|
190
|
+
});
|
|
150
191
|
}
|
|
151
|
-
if (frontmatter
|
|
152
|
-
|
|
192
|
+
if (frontmatter !== null) {
|
|
193
|
+
if (frontmatter.name !== expectedVariant) {
|
|
194
|
+
violations.push({
|
|
195
|
+
path: 'definition',
|
|
196
|
+
message: `Generated Claude definition name does not match ${expectedVariant}.`,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
if (frontmatter.model !== selected.model) {
|
|
200
|
+
violations.push({
|
|
201
|
+
path: 'definition',
|
|
202
|
+
message: `Generated Claude definition model does not match selected model ${selected.model}.`,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
if (frontmatter.effort !== selected.effort) {
|
|
206
|
+
violations.push({
|
|
207
|
+
path: 'definition',
|
|
208
|
+
message: `Generated Claude definition effort does not match selected effort ${selected.effort}.`,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
153
211
|
}
|
|
154
|
-
if (launch
|
|
155
|
-
launch.model !==
|
|
156
|
-
|
|
212
|
+
if (launch !== null &&
|
|
213
|
+
launch.model !== undefined &&
|
|
214
|
+
launch.model !== selected.model) {
|
|
215
|
+
violations.push({
|
|
216
|
+
path: 'payload.model',
|
|
217
|
+
message: `Per-call Claude model ${launch.model} conflicts with generated definition model ${selected.model}.`,
|
|
218
|
+
});
|
|
157
219
|
}
|
|
158
|
-
if (provider.modelAxis !== `selected:${
|
|
159
|
-
|
|
220
|
+
if (provider.modelAxis !== `selected:${selected.model}`) {
|
|
221
|
+
violations.push({
|
|
222
|
+
path: 'resolution.providers.claude.modelAxis',
|
|
223
|
+
message: 'Claude resolver model axis disagrees with its target.',
|
|
224
|
+
});
|
|
160
225
|
}
|
|
161
|
-
if (provider.effortAxis !== `selected:${
|
|
162
|
-
|
|
226
|
+
if (provider.effortAxis !== `selected:${selected.effort}`) {
|
|
227
|
+
violations.push({
|
|
228
|
+
path: 'resolution.providers.claude.effortAxis',
|
|
229
|
+
message: 'Claude resolver effort axis disagrees with its target.',
|
|
230
|
+
});
|
|
163
231
|
}
|
|
232
|
+
return violations;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Validate the managed resolver result against the generated provider role and
|
|
236
|
+
* the exact payload that will be launched. This is the production boundary
|
|
237
|
+
* used by the project dispatch-record producer for managed Claude dispatches.
|
|
238
|
+
* It stops at the first failure; {@link collectClaudeLaunchViolations} reports
|
|
239
|
+
* every failure for a caller building its input.
|
|
240
|
+
*/
|
|
241
|
+
export function acceptClaudeLaunchEnvelope(input) {
|
|
242
|
+
const resolution = resolutionSchema.parse(input.resolution);
|
|
243
|
+
const provider = resolution.providers.claude;
|
|
244
|
+
const launch = launchPayloadSchema.parse(input.launch);
|
|
245
|
+
const [first] = launchConsistencyViolations(resolution, launch, input.definition);
|
|
246
|
+
if (first)
|
|
247
|
+
throw new Error(first.message);
|
|
248
|
+
const baseRole = provider.selection.role === 'reviewer'
|
|
249
|
+
? 'oat-reviewer'
|
|
250
|
+
: 'oat-phase-implementer';
|
|
164
251
|
return {
|
|
165
252
|
schemaVersion: 1,
|
|
166
253
|
provider: 'claude',
|
|
167
254
|
resolverRole: provider.selection.role,
|
|
168
255
|
baseRole,
|
|
169
|
-
variant:
|
|
256
|
+
variant: buildClaudeEffortVariantName({
|
|
257
|
+
agentName: baseRole,
|
|
258
|
+
model: provider.selection.target.model,
|
|
259
|
+
effort: provider.selection.target.effort,
|
|
260
|
+
}),
|
|
170
261
|
model: provider.selection.target.model,
|
|
171
262
|
capabilityEvidence: provider.selection.target.capabilityEvidence,
|
|
172
263
|
effort: provider.selection.target.effort,
|
|
@@ -175,6 +266,195 @@ export function acceptClaudeLaunchEnvelope(input) {
|
|
|
175
266
|
payload: launch,
|
|
176
267
|
};
|
|
177
268
|
}
|
|
269
|
+
const LAUNCH_CONSISTENCY_SKIPPED = 'claudeLaunch consistency checks (need a parsed resolution)';
|
|
270
|
+
const PAYLOAD_CHECKS_SKIPPED = 'claudeLaunch payload variant and model checks (need a parsed payload)';
|
|
271
|
+
const ACTION_ROLE_SKIPPED = 'recordBase action/role check (needs a parsed resolution)';
|
|
272
|
+
/**
|
|
273
|
+
* Report every `claudeLaunch` violation in one pass: each schema issue in the
|
|
274
|
+
* resolution and the payload, then every consistency check the parsed inputs
|
|
275
|
+
* allow. Consistency checks depend on a parsed resolution, and the two payload
|
|
276
|
+
* checks on a parsed payload; those are skipped and named in `skipped` rather
|
|
277
|
+
* than reported against a value that failed to parse.
|
|
278
|
+
*/
|
|
279
|
+
export function collectClaudeLaunchViolations(claudeLaunch) {
|
|
280
|
+
if (!claudeLaunch ||
|
|
281
|
+
typeof claudeLaunch !== 'object' ||
|
|
282
|
+
Array.isArray(claudeLaunch)) {
|
|
283
|
+
return {
|
|
284
|
+
resolverRole: null,
|
|
285
|
+
violations: [
|
|
286
|
+
{
|
|
287
|
+
stage: 'claudeLaunch',
|
|
288
|
+
path: '<root>',
|
|
289
|
+
message: 'Managed Claude claudeLaunch must be a JSON object with resolution, definition, and payload.',
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
skipped: [LAUNCH_CONSISTENCY_SKIPPED],
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
const launchInput = claudeLaunch;
|
|
296
|
+
const violations = [];
|
|
297
|
+
const resolution = resolutionSchema.safeParse(launchInput.resolution);
|
|
298
|
+
if (!resolution.success) {
|
|
299
|
+
violations.push(...zodIssueViolations('claudeLaunch', 'resolution', resolution.error));
|
|
300
|
+
}
|
|
301
|
+
const launch = launchPayloadSchema.safeParse(launchInput.payload);
|
|
302
|
+
if (!launch.success) {
|
|
303
|
+
violations.push(...zodIssueViolations('claudeLaunch', 'payload', launch.error));
|
|
304
|
+
}
|
|
305
|
+
if (!resolution.success) {
|
|
306
|
+
return {
|
|
307
|
+
resolverRole: null,
|
|
308
|
+
violations,
|
|
309
|
+
skipped: [LAUNCH_CONSISTENCY_SKIPPED],
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
const skipped = launch.success ? [] : [PAYLOAD_CHECKS_SKIPPED];
|
|
313
|
+
violations.push(...launchConsistencyViolations(resolution.data, launch.success ? launch.data : null, String(launchInput.definition ?? '')).map((violation) => ({ stage: 'claudeLaunch', ...violation })));
|
|
314
|
+
return {
|
|
315
|
+
resolverRole: resolution.data.providers.claude.selection.role,
|
|
316
|
+
violations,
|
|
317
|
+
skipped,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
const PROTECTED_FIELD_MASK = Object.fromEntries(PROTECTED_RECORD_FIELDS.map((field) => [field, true]));
|
|
321
|
+
/**
|
|
322
|
+
* The caller-authored half of a managed record: the closed generic field set
|
|
323
|
+
* with every derived field omitted. The cross-field rules are applied
|
|
324
|
+
* separately (see {@link crossFieldViolations}) because a Zod refinement never
|
|
325
|
+
* runs once the object parse aborts, which would push those rules to a later
|
|
326
|
+
* run.
|
|
327
|
+
*/
|
|
328
|
+
const managedRecordBaseSchema = genericDispatchRecordBaseSchema.omit(PROTECTED_FIELD_MASK);
|
|
329
|
+
/**
|
|
330
|
+
* Run the generic cross-field rules directly on the caller-owned fields,
|
|
331
|
+
* whether or not the object parse succeeded. The rules read every field
|
|
332
|
+
* defensively, so a missing or mistyped field elsewhere does not hide a
|
|
333
|
+
* cross-field violation until the next run.
|
|
334
|
+
*/
|
|
335
|
+
function crossFieldViolations(callerOwned) {
|
|
336
|
+
const violations = [];
|
|
337
|
+
const context = {
|
|
338
|
+
path: [],
|
|
339
|
+
addIssue: (issue) => {
|
|
340
|
+
violations.push({
|
|
341
|
+
stage: 'recordBase',
|
|
342
|
+
path: formatIssuePath(null, issue.path ?? []),
|
|
343
|
+
message: issue.message ?? 'Invalid',
|
|
344
|
+
});
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
refineGenericDispatchRecord(callerOwned, context);
|
|
348
|
+
return violations;
|
|
349
|
+
}
|
|
350
|
+
function actionConflict(resolverRole, action) {
|
|
351
|
+
return (resolverRole === 'reviewer' && action !== 'review') ||
|
|
352
|
+
(resolverRole === 'implementer' &&
|
|
353
|
+
action !== 'implementation' &&
|
|
354
|
+
action !== 'fix')
|
|
355
|
+
? `Managed Claude resolver role ${resolverRole} conflicts with record action ${String(action)}.`
|
|
356
|
+
: null;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Report every `recordBase` violation in one pass: each derived field that is
|
|
360
|
+
* present, the action/role check, every missing, mistyped, or unknown field,
|
|
361
|
+
* every absolute path in an identity or control field, and every sensitive key
|
|
362
|
+
* or value. The action/role check needs a parsed resolution and is skipped
|
|
363
|
+
* without one.
|
|
364
|
+
*/
|
|
365
|
+
export function collectClaudeRecordBaseViolations(candidate, resolverRole) {
|
|
366
|
+
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
|
|
367
|
+
return {
|
|
368
|
+
violations: [
|
|
369
|
+
{
|
|
370
|
+
stage: 'recordBase',
|
|
371
|
+
path: '<root>',
|
|
372
|
+
message: 'Managed Claude dispatch recordBase must be a JSON object.',
|
|
373
|
+
},
|
|
374
|
+
],
|
|
375
|
+
skipped: [],
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
const base = candidate;
|
|
379
|
+
const protectedFields = new Set(PROTECTED_RECORD_FIELDS);
|
|
380
|
+
const violations = [];
|
|
381
|
+
for (const field of PROTECTED_RECORD_FIELDS) {
|
|
382
|
+
if (Object.hasOwn(base, field)) {
|
|
383
|
+
violations.push({
|
|
384
|
+
stage: 'recordBase',
|
|
385
|
+
path: field,
|
|
386
|
+
message: `Managed Claude dispatch recordBase must omit derived field ${field}.`,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const skipped = [];
|
|
391
|
+
if (resolverRole === null) {
|
|
392
|
+
skipped.push(ACTION_ROLE_SKIPPED);
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
const conflict = actionConflict(resolverRole, base.action);
|
|
396
|
+
if (conflict !== null) {
|
|
397
|
+
violations.push({
|
|
398
|
+
stage: 'recordBase',
|
|
399
|
+
path: 'action',
|
|
400
|
+
message: conflict,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
const callerOwned = Object.fromEntries(Object.entries(base).filter(([key]) => !protectedFields.has(key)));
|
|
405
|
+
const parsed = managedRecordBaseSchema.safeParse(callerOwned);
|
|
406
|
+
if (!parsed.success) {
|
|
407
|
+
violations.push(...zodIssueViolations('recordBase', null, parsed.error));
|
|
408
|
+
}
|
|
409
|
+
violations.push(...crossFieldViolations(callerOwned));
|
|
410
|
+
for (const [field, value] of Object.entries(identityFieldsOf(callerOwned))) {
|
|
411
|
+
violations.push(...collectAbsolutePathViolations(value, field).map((violation) => ({
|
|
412
|
+
stage: 'recordBase',
|
|
413
|
+
...violation,
|
|
414
|
+
})));
|
|
415
|
+
}
|
|
416
|
+
violations.push(...collectSensitiveDispatchContent(base, 'recordBase').map((violation) => ({
|
|
417
|
+
stage: 'recordBase',
|
|
418
|
+
path: violation.path.slice('recordBase.'.length) || '<root>',
|
|
419
|
+
message: violation.message,
|
|
420
|
+
})));
|
|
421
|
+
return { violations, skipped };
|
|
422
|
+
}
|
|
423
|
+
function oneLine(text) {
|
|
424
|
+
return text.replace(/\r?\n/g, ' ');
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* One error for a whole validation run: a header, then one
|
|
428
|
+
* `stage path: message` line per violation, then the checks that were skipped
|
|
429
|
+
* because they depend on a stage that failed to parse.
|
|
430
|
+
*/
|
|
431
|
+
export class ManagedClaudeDispatchValidationError extends Error {
|
|
432
|
+
violations;
|
|
433
|
+
skipped;
|
|
434
|
+
constructor(violations, skipped = []) {
|
|
435
|
+
// Scrub each violation on its own before joining. A scrub over the joined
|
|
436
|
+
// text lets one unterminated secret (a private-key header with no END
|
|
437
|
+
// marker) consume every later line; per violation it ends with that
|
|
438
|
+
// violation. Echoed newlines are folded so each violation stays one line
|
|
439
|
+
// and the line count matches the header.
|
|
440
|
+
const scrubbed = violations.map((violation) => ({
|
|
441
|
+
stage: violation.stage,
|
|
442
|
+
path: oneLine(redactSensitiveValues(violation.path)),
|
|
443
|
+
message: oneLine(redactSensitiveValues(violation.message)),
|
|
444
|
+
}));
|
|
445
|
+
const lines = [
|
|
446
|
+
`Managed Claude dispatch input has ${scrubbed.length} violation${scrubbed.length === 1 ? '' : 's'}:`,
|
|
447
|
+
...scrubbed.map(({ stage, path, message }) => `${stage} ${path}: ${message}`),
|
|
448
|
+
];
|
|
449
|
+
if (skipped.length > 0) {
|
|
450
|
+
lines.push(`Skipped until the above are fixed: ${skipped.join('; ')}.`);
|
|
451
|
+
}
|
|
452
|
+
super(lines.join('\n'));
|
|
453
|
+
this.name = 'ManagedClaudeDispatchValidationError';
|
|
454
|
+
this.violations = scrubbed;
|
|
455
|
+
this.skipped = skipped;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
178
458
|
function recordBase(value) {
|
|
179
459
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
180
460
|
throw new Error('Managed Claude dispatch recordBase must be a JSON object.');
|
|
@@ -190,13 +470,9 @@ function recordBase(value) {
|
|
|
190
470
|
/** Build configured invocation evidence only from an accepted launch envelope. */
|
|
191
471
|
export function buildClaudeDispatchRecord(input) {
|
|
192
472
|
const base = recordBase(input.recordBase);
|
|
193
|
-
const
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
action !== 'implementation' &&
|
|
197
|
-
action !== 'fix')) {
|
|
198
|
-
throw new Error(`Managed Claude resolver role ${input.envelope.resolverRole} conflicts with record action ${String(action)}.`);
|
|
199
|
-
}
|
|
473
|
+
const conflict = actionConflict(input.envelope.resolverRole, base.action);
|
|
474
|
+
if (conflict !== null)
|
|
475
|
+
throw new Error(conflict);
|
|
200
476
|
return parseGenericDispatchRecord({
|
|
201
477
|
...base,
|
|
202
478
|
provider: 'claude',
|
|
@@ -8,7 +8,7 @@ function parsePaths(value) {
|
|
|
8
8
|
return undefined;
|
|
9
9
|
}
|
|
10
10
|
export function transformCanonicalToClaudeRule(canonicalContent, canonicalPath) {
|
|
11
|
-
const rule = parseCanonicalRuleMarkdown(canonicalContent);
|
|
11
|
+
const rule = parseCanonicalRuleMarkdown(canonicalContent, canonicalPath);
|
|
12
12
|
const frontmatter = rule.activation === 'glob' && rule.globs ? { paths: rule.globs } : null;
|
|
13
13
|
return appendGeneratedMarker(renderMarkdownWithFrontmatter(frontmatter, rule.body), canonicalPath);
|
|
14
14
|
}
|
|
@@ -33,7 +33,7 @@ function isRepoWideApplyTo(globs) {
|
|
|
33
33
|
return globs?.length === 1 && globs[0] === '**';
|
|
34
34
|
}
|
|
35
35
|
export function transformCanonicalToCopilotRule(canonicalContent, canonicalPath) {
|
|
36
|
-
const rule = parseCanonicalRuleMarkdown(canonicalContent);
|
|
36
|
+
const rule = parseCanonicalRuleMarkdown(canonicalContent, canonicalPath);
|
|
37
37
|
if (rule.activation === 'glob' && rule.globs) {
|
|
38
38
|
assertCopilotGlobsAreRepresentable(rule.globs);
|
|
39
39
|
}
|
|
@@ -24,7 +24,7 @@ function buildCursorFrontmatter(activation, description, globs) {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
export function transformCanonicalToCursorRule(canonicalContent, canonicalPath) {
|
|
27
|
-
const rule = parseCanonicalRuleMarkdown(canonicalContent);
|
|
27
|
+
const rule = parseCanonicalRuleMarkdown(canonicalContent, canonicalPath);
|
|
28
28
|
const frontmatter = buildCursorFrontmatter(rule.activation, rule.description, rule.globs);
|
|
29
29
|
return appendGeneratedMarker(renderMarkdownWithFrontmatter(frontmatter, rule.body), canonicalPath);
|
|
30
30
|
}
|
|
@@ -49,6 +49,19 @@ export declare function redactAbsolutePaths(value: string): string;
|
|
|
49
49
|
* a malformed identifier.
|
|
50
50
|
*/
|
|
51
51
|
export declare function redactAbsolutePathsDeep<T>(value: T): T;
|
|
52
|
+
/** One located validation violation. The message never echoes the value. */
|
|
53
|
+
export interface DispatchValueViolation {
|
|
54
|
+
path: string;
|
|
55
|
+
message: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Collect every absolute path in an identity or control value, in the same
|
|
59
|
+
* depth-first order {@link assertNoAbsolutePath} visits them, so a caller that
|
|
60
|
+
* reports all violations at once and one that stops at the first agree on
|
|
61
|
+
* which hit comes first. Each message names the field, never the path found
|
|
62
|
+
* there, so the report itself cannot disclose it.
|
|
63
|
+
*/
|
|
64
|
+
export declare function collectAbsolutePathViolations(value: unknown, path: string): DispatchValueViolation[];
|
|
52
65
|
/**
|
|
53
66
|
* Recursively reject any absolute path. Used for identity and control fields,
|
|
54
67
|
* where a path is never a legitimate value and silently rewriting one would
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"absolute-paths.d.ts","sourceRoot":"","sources":["../../../src/providers/identity/absolute-paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AA6BH,6EAA6E;AAC7E,eAAO,MAAM,aAAa,oBAAoB,CAAC;AAM/C;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE3D;AAYD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOzD;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CActD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"absolute-paths.d.ts","sourceRoot":"","sources":["../../../src/providers/identity/absolute-paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AA6BH,6EAA6E;AAC7E,eAAO,MAAM,aAAa,oBAAoB,CAAC;AAM/C;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE3D;AAYD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOzD;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CActD;AAED,4EAA4E;AAC5E,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,MAAM,GACX,sBAAsB,EAAE,CAoB1B;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAGvE;AAED;;;;;;;;GAQG;AACH,wBAAgB,sCAAsC,CACpD,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAChD,IAAI,CAIN"}
|
|
@@ -107,26 +107,39 @@ export function redactAbsolutePathsDeep(value) {
|
|
|
107
107
|
return value;
|
|
108
108
|
}
|
|
109
109
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
110
|
+
* Collect every absolute path in an identity or control value, in the same
|
|
111
|
+
* depth-first order {@link assertNoAbsolutePath} visits them, so a caller that
|
|
112
|
+
* reports all violations at once and one that stops at the first agree on
|
|
113
|
+
* which hit comes first. Each message names the field, never the path found
|
|
114
|
+
* there, so the report itself cannot disclose it.
|
|
113
115
|
*/
|
|
114
|
-
export function
|
|
116
|
+
export function collectAbsolutePathViolations(value, path) {
|
|
115
117
|
if (typeof value === 'string') {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
return containsAbsolutePath(value)
|
|
119
|
+
? [
|
|
120
|
+
{
|
|
121
|
+
path,
|
|
122
|
+
message: `A dispatch record must not carry an absolute filesystem path at ${path}.`,
|
|
123
|
+
},
|
|
124
|
+
]
|
|
125
|
+
: [];
|
|
120
126
|
}
|
|
121
127
|
if (Array.isArray(value)) {
|
|
122
|
-
value.
|
|
123
|
-
return;
|
|
128
|
+
return value.flatMap((entry, index) => collectAbsolutePathViolations(entry, `${path}[${index}]`));
|
|
124
129
|
}
|
|
125
130
|
if (value === null || typeof value !== 'object')
|
|
126
|
-
return;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
131
|
+
return [];
|
|
132
|
+
return Object.entries(value).flatMap(([key, entry]) => collectAbsolutePathViolations(entry, `${path}.${key}`));
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Recursively reject any absolute path. Used for identity and control fields,
|
|
136
|
+
* where a path is never a legitimate value and silently rewriting one would
|
|
137
|
+
* corrupt the identifier it claims to be.
|
|
138
|
+
*/
|
|
139
|
+
export function assertNoAbsolutePath(value, path) {
|
|
140
|
+
const [first] = collectAbsolutePathViolations(value, path);
|
|
141
|
+
if (first)
|
|
142
|
+
throw new Error(first.message);
|
|
130
143
|
}
|
|
131
144
|
/**
|
|
132
145
|
* The publication postcondition, scoped to what is actually guaranteed.
|