@fitlab-ai/agent-infra 0.9.8 → 0.9.10
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/bin/internal-cli.js +11 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +58 -21
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +25 -11
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +173 -10
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +127 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +63 -92
- package/dist/lib/agent-clients/adapters/codex-sandbox.js +72 -11
- package/dist/lib/agent-clients/adapters/index.js +3 -1
- package/dist/lib/agent-clients/adapters/traecli.js +66 -0
- package/dist/lib/agent-clients/tokens.js +2 -2
- package/dist/lib/agent-clients/types.js +2 -1
- package/dist/lib/defaults.json +5 -0
- package/dist/lib/git/workflow.js +10 -1
- package/dist/lib/internal/codex-lifecycle.js +20 -6
- package/dist/lib/internal/codex-sandbox-controller.js +9 -15
- package/dist/lib/internal/git-workflow.js +5 -7
- package/dist/lib/internal/platform-pr.js +20 -7
- package/dist/lib/internal/release-workflow.js +2 -1
- package/dist/lib/internal/sandbox-control.js +79 -2
- package/dist/lib/internal/task-finalization.js +54 -0
- package/dist/lib/internal/task-orchestration.js +156 -291
- package/dist/lib/{task/commands/validate.js → internal/task-validate.js} +75 -42
- package/dist/lib/platform/pr-summary.js +48 -10
- package/dist/lib/platform/pull-requests.js +205 -41
- package/dist/lib/run/tui.js +5 -2
- package/dist/lib/sandbox/commands/rm.js +316 -55
- package/dist/lib/sandbox/control/client.js +190 -6
- package/dist/lib/sandbox/control/controller-registration.js +275 -0
- package/dist/lib/sandbox/control/executor.js +140 -5
- package/dist/lib/sandbox/control/lifecycle.js +3 -1
- package/dist/lib/sandbox/control/protocol.js +67 -6
- package/dist/lib/sandbox/index.js +5 -3
- package/dist/lib/sandbox/managed-fs.js +5 -0
- package/dist/lib/sandbox/task-resolver.js +50 -6
- package/dist/lib/sandbox/workspace-identity.js +27 -0
- package/dist/lib/sandbox/worktree-safety.js +215 -2
- package/dist/lib/task/codex-orchestration.js +161 -39
- package/dist/lib/task/commands/status.js +59 -1
- package/dist/lib/task/commit-operation.js +335 -0
- package/dist/lib/task/commit-policy.js +18 -0
- package/dist/lib/task/delegation-receipts.js +248 -6
- package/dist/lib/task/finalization.js +593 -0
- package/dist/lib/task/index.js +0 -9
- package/dist/lib/task/operation-outcome.js +33 -0
- package/dist/lib/task/orchestration.js +187 -761
- package/dist/lib/task/short-id.js +59 -1
- package/dist/lib/task/task-execution-lock.js +6 -1
- package/dist/lib/task/verification-engine.js +110 -60
- package/dist/lib/task/verification.js +2 -1
- package/dist/lib/task/workflow-warning-intents.js +50 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +61 -19
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +37 -10
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +276 -12
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +163 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +9 -1
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +91 -118
- package/lib/agent-clients/adapters/codex-sandbox.ts +89 -14
- package/lib/agent-clients/adapters/index.ts +3 -1
- package/lib/agent-clients/adapters/traecli.ts +67 -0
- package/lib/agent-clients/tokens.ts +2 -2
- package/lib/agent-clients/types.ts +2 -1
- package/lib/defaults.json +5 -0
- package/lib/git/workflow.ts +11 -2
- package/lib/internal/codex-lifecycle.ts +19 -6
- package/lib/internal/codex-sandbox-controller.ts +9 -14
- package/lib/internal/git-workflow.ts +5 -6
- package/lib/internal/platform-pr.ts +14 -7
- package/lib/internal/release-workflow.ts +2 -1
- package/lib/internal/sandbox-control.ts +92 -2
- package/lib/internal/task-finalization.ts +68 -0
- package/lib/internal/task-orchestration.ts +20 -103
- package/lib/{task/commands/validate.ts → internal/task-validate.ts} +75 -40
- package/lib/platform/pr-summary.ts +55 -11
- package/lib/platform/pull-requests.ts +211 -41
- package/lib/run/tui.ts +5 -3
- package/lib/sandbox/commands/rm.ts +413 -61
- package/lib/sandbox/control/client.ts +255 -6
- package/lib/sandbox/control/controller-registration.ts +352 -0
- package/lib/sandbox/control/executor.ts +169 -5
- package/lib/sandbox/control/lifecycle.ts +2 -1
- package/lib/sandbox/control/protocol.ts +80 -7
- package/lib/sandbox/index.ts +5 -3
- package/lib/sandbox/managed-fs.ts +5 -0
- package/lib/sandbox/task-resolver.ts +58 -7
- package/lib/sandbox/workspace-identity.ts +50 -2
- package/lib/sandbox/workspace-view.ts +3 -3
- package/lib/sandbox/worktree-safety.ts +236 -2
- package/lib/task/codex-orchestration.ts +168 -40
- package/lib/task/commands/status.ts +78 -1
- package/lib/task/commit-operation.ts +430 -0
- package/lib/task/commit-policy.ts +30 -0
- package/lib/task/delegation-receipts.ts +263 -4
- package/lib/task/finalization.ts +761 -0
- package/lib/task/index.ts +0 -8
- package/lib/task/operation-outcome.ts +48 -0
- package/lib/task/orchestration.ts +218 -982
- package/lib/task/short-id.ts +65 -1
- package/lib/task/task-execution-lock.ts +10 -1
- package/lib/task/verification-engine.ts +115 -63
- package/lib/task/verification.ts +3 -2
- package/lib/task/workflow-warning-intents.ts +57 -2
- package/package.json +2 -2
- package/templates/.agents/QUICKSTART.en.md +3 -4
- package/templates/.agents/QUICKSTART.zh-CN.md +3 -4
- package/templates/.agents/hooks/lifecycle-delegation.js +1 -21
- package/templates/.agents/rules/README.en.md +1 -0
- package/templates/.agents/rules/README.zh-CN.md +1 -0
- package/templates/.agents/rules/compatibility-policy.en.md +39 -0
- package/templates/.agents/rules/compatibility-policy.zh-CN.md +39 -0
- package/templates/.agents/rules/lifecycle-orchestration.en.md +4 -4
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +4 -4
- package/templates/.agents/rules/pr-sync.en.md +1 -1
- package/templates/.agents/rules/pr-sync.github.en.md +2 -1
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +2 -0
- package/templates/.agents/rules/pr-sync.zh-CN.md +1 -0
- package/templates/.agents/rules/review-method.en.md +1 -0
- package/templates/.agents/rules/review-method.zh-CN.md +1 -0
- package/templates/.agents/rules/task-management.en.md +2 -2
- package/templates/.agents/rules/task-management.zh-CN.md +2 -2
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -0
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -4
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -0
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -0
- package/templates/.agents/skills/commit/SKILL.en.md +41 -125
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +52 -123
- package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +14 -43
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +14 -49
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -35
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -35
- package/templates/.agents/skills/complete-task/SKILL.en.md +31 -38
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +31 -38
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +17 -9
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +17 -9
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +41 -0
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +41 -0
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +2 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -1
- package/templates/.agents/skills/review-code/reference/migration-risks.en.md +8 -6
- package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +8 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +1 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +24 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
- package/templates/.codex/hooks.json +4 -4
- package/templates/.trae/skills/update-agent-infra.en.md +8 -0
- package/templates/.trae/skills/update-agent-infra.zh-CN.md +8 -0
- package/dist/lib/task/commit-finalization.js +0 -305
- package/dist/lib/task/commit-intent.js +0 -165
- package/lib/task/commit-finalization.ts +0 -419
- package/lib/task/commit-intent.ts +0 -230
package/lib/task/short-id.ts
CHANGED
|
@@ -201,6 +201,10 @@ type RegistrySchema = {
|
|
|
201
201
|
ids: Record<string, string>;
|
|
202
202
|
};
|
|
203
203
|
|
|
204
|
+
type ShortIdRegistryInspection =
|
|
205
|
+
| { status: 'valid'; shortIds: Map<string, string> }
|
|
206
|
+
| { status: 'missing' | 'invalid'; error: { code: string; message: string } };
|
|
207
|
+
|
|
204
208
|
type ShortIdMutationEffect = 'alloc' | 'release' | 'none';
|
|
205
209
|
type ShortIdMutationResult = {
|
|
206
210
|
effect: 'allocated' | 'released' | 'unchanged';
|
|
@@ -446,6 +450,64 @@ function loadShortIdByTaskId(repoRoot: string): Map<string, string> {
|
|
|
446
450
|
return map;
|
|
447
451
|
}
|
|
448
452
|
|
|
453
|
+
function inspectShortIdRegistry(repoRoot: string): ShortIdRegistryInspection {
|
|
454
|
+
const registryPath = path.join(repoRoot, '.agents', 'workspace', 'active', REGISTRY_NAME);
|
|
455
|
+
if (!fs.existsSync(registryPath)) {
|
|
456
|
+
return {
|
|
457
|
+
status: 'missing',
|
|
458
|
+
error: {
|
|
459
|
+
code: 'SHORT_ID_REGISTRY_NOT_FOUND',
|
|
460
|
+
message: 'canonical short-id registry is missing'
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
let raw: string;
|
|
466
|
+
try {
|
|
467
|
+
raw = fs.readFileSync(registryPath, 'utf8');
|
|
468
|
+
} catch {
|
|
469
|
+
return {
|
|
470
|
+
status: 'invalid',
|
|
471
|
+
error: {
|
|
472
|
+
code: 'SHORT_ID_REGISTRY_READ_FAILED',
|
|
473
|
+
message: 'canonical short-id registry is unreadable'
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
let parsed: unknown;
|
|
479
|
+
try {
|
|
480
|
+
parsed = JSON.parse(raw);
|
|
481
|
+
} catch {
|
|
482
|
+
return {
|
|
483
|
+
status: 'invalid',
|
|
484
|
+
error: {
|
|
485
|
+
code: 'SHORT_ID_REGISTRY_INVALID_JSON',
|
|
486
|
+
message: 'canonical short-id registry contains invalid JSON'
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
const registry = validateRegistry(parsed, registryPath, configuredShortIdLength(repoRoot));
|
|
493
|
+
const shortIds = new Map<string, string>();
|
|
494
|
+
for (const [key, taskId] of Object.entries(registry.ids)) shortIds.set(taskId, key);
|
|
495
|
+
return { status: 'valid', shortIds };
|
|
496
|
+
} catch (error) {
|
|
497
|
+
return {
|
|
498
|
+
status: 'invalid',
|
|
499
|
+
error: {
|
|
500
|
+
code: typeof (error as { code?: unknown }).code === 'string'
|
|
501
|
+
? (error as { code: string }).code
|
|
502
|
+
: 'SHORT_ID_REGISTRY_INVALID_SCHEMA',
|
|
503
|
+
message: (error as { code?: unknown }).code === 'SHORT_ID_REGISTRY_DUPLICATE_TASK'
|
|
504
|
+
? 'canonical short-id registry contains duplicate task entries'
|
|
505
|
+
: 'canonical short-id registry has invalid schema'
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
449
511
|
/**
|
|
450
512
|
* Resolve a branch to its active-task short id (`NN`), or `null` when no
|
|
451
513
|
* active task is bound to that branch.
|
|
@@ -485,6 +547,7 @@ export {
|
|
|
485
547
|
resolveShortIdReadOnly,
|
|
486
548
|
lookupShortIdByBranch,
|
|
487
549
|
loadShortIdByTaskId,
|
|
550
|
+
inspectShortIdRegistry,
|
|
488
551
|
configuredShortIdLength,
|
|
489
552
|
mutateShortIdRegistry,
|
|
490
553
|
executeShortIdCommand
|
|
@@ -497,5 +560,6 @@ export type {
|
|
|
497
560
|
ShortIdMutationEffect,
|
|
498
561
|
ShortIdMutationResult,
|
|
499
562
|
ShortIdCommandRequest,
|
|
500
|
-
ShortIdCommandResult
|
|
563
|
+
ShortIdCommandResult,
|
|
564
|
+
ShortIdRegistryInspection
|
|
501
565
|
};
|
|
@@ -272,6 +272,7 @@ function withTaskExecutionLock<T>(
|
|
|
272
272
|
return callback();
|
|
273
273
|
} catch (error) {
|
|
274
274
|
if (error instanceof TaskExecutionLockError) throw error;
|
|
275
|
+
if (error instanceof Error && error.name === 'OrchestrationStateError') throw error;
|
|
275
276
|
throw new TaskExecutionLockError(
|
|
276
277
|
'ORCHESTRATION_LOCK_FAILED',
|
|
277
278
|
'lifecycle task lock operation failed',
|
|
@@ -294,5 +295,13 @@ function withTaskExecutionLock<T>(
|
|
|
294
295
|
}
|
|
295
296
|
}
|
|
296
297
|
|
|
297
|
-
|
|
298
|
+
function withRepositoryMutationLock<T>(
|
|
299
|
+
repoRoot: string,
|
|
300
|
+
callback: () => T,
|
|
301
|
+
options: TaskExecutionLockOptions = {}
|
|
302
|
+
): T {
|
|
303
|
+
return withTaskExecutionLock(repoRoot, '__repository_mutation__', 'repository-mutation', callback, options);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export { TaskExecutionLockError, lockKey, mapLinkError, withRepositoryMutationLock, withTaskExecutionLock };
|
|
298
307
|
export type { LinkDisposition, LinkOperation, TaskExecutionLockErrorCode, TaskExecutionLockOptions, TaskExecutionLockOwner };
|
|
@@ -27,6 +27,7 @@ import type { LedgerRow } from "./ledger.ts";
|
|
|
27
27
|
import { equalCounts, parseReviewSummary } from "./review-artifacts.ts";
|
|
28
28
|
import { loadVerificationConfig } from "./verification-config.ts";
|
|
29
29
|
import { snapshotReview } from "../git/review-snapshot.ts";
|
|
30
|
+
import { OrchestrationStateError, readRun } from "./orchestration.ts";
|
|
30
31
|
|
|
31
32
|
const TASK_ENUMS = {
|
|
32
33
|
type: ["feature", "bugfix", "refactor", "docs", "chore"],
|
|
@@ -123,6 +124,8 @@ function runCheck(type: any, context: any, shared: any): any {
|
|
|
123
124
|
return checkReviewFact(context);
|
|
124
125
|
case "post-review-commit":
|
|
125
126
|
return checkPostReviewCommit(context);
|
|
127
|
+
case "required-pr-delivery":
|
|
128
|
+
return checkRequiredPrDelivery(context);
|
|
126
129
|
case "orchestration-state":
|
|
127
130
|
return checkOrchestrationState(context);
|
|
128
131
|
case "orchestration-evidence":
|
|
@@ -142,40 +145,19 @@ function runCheck(type: any, context: any, shared: any): any {
|
|
|
142
145
|
}
|
|
143
146
|
}
|
|
144
147
|
|
|
145
|
-
function isFailClosedLegacyPause(run: any): boolean {
|
|
146
|
-
if (
|
|
147
|
-
run.schemaVersion !== 1
|
|
148
|
-
|| run.status !== 'paused'
|
|
149
|
-
|| run.pause?.recoverable !== false
|
|
150
|
-
|| !exactText(run.pause?.message)
|
|
151
|
-
|| !Array.isArray(run.receipts)
|
|
152
|
-
) {
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
|
-
if (run.pause.code === 'ORCHESTRATION_HISTORICAL_EFFORT_UNVERIFIED') {
|
|
156
|
-
return run.receipts.length > 0 && run.pendingDelegation === null;
|
|
157
|
-
}
|
|
158
|
-
if (run.pause.code === 'ORCHESTRATION_DELEGATION_BUSY') {
|
|
159
|
-
return run.pendingDelegation != null && run.pendingDelegation.status !== 'sealed';
|
|
160
|
-
}
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
148
|
function checkOrchestrationState({ taskDir }: any): any {
|
|
165
149
|
const file = path.join(taskDir, 'orchestration.json');
|
|
166
150
|
const stat = safeStat(file);
|
|
167
151
|
if (!stat?.isFile()) return failResult('orchestration-state', 'orchestration.json is missing');
|
|
168
152
|
let run: any;
|
|
169
153
|
try {
|
|
170
|
-
run =
|
|
154
|
+
run = readRun(taskDir);
|
|
171
155
|
} catch (error) {
|
|
172
|
-
|
|
156
|
+
const message = error instanceof OrchestrationStateError ? error.message : String(error);
|
|
157
|
+
return failResult('orchestration-state', `Invalid orchestration.json: ${message}`);
|
|
173
158
|
}
|
|
174
|
-
if (
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
if (![2, 3].includes(run.schemaVersion) || !['paused', 'completed'].includes(run.status)) {
|
|
178
|
-
return failResult('orchestration-state', `Expected paused or completed run, received '${run.status}'`);
|
|
159
|
+
if (!run || !['paused', 'completed'].includes(run.status)) {
|
|
160
|
+
return failResult('orchestration-state', `Expected paused or completed run, received '${run?.status ?? 'missing'}'`);
|
|
179
161
|
}
|
|
180
162
|
if (run.status === 'paused' && (!run.pause?.code || !run.pause?.message)) {
|
|
181
163
|
return failResult('orchestration-state', 'Paused run requires a stable pause code and message');
|
|
@@ -242,12 +224,10 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
|
|
|
242
224
|
if (!stat?.isFile()) return failResult('orchestration-evidence', 'orchestration.json is missing');
|
|
243
225
|
let run: any;
|
|
244
226
|
try {
|
|
245
|
-
run =
|
|
227
|
+
run = readRun(taskDir);
|
|
246
228
|
} catch (error) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (isFailClosedLegacyPause(run)) {
|
|
250
|
-
return passResult('orchestration-evidence', 'Legacy orchestration evidence is preserved in a fail-closed pause');
|
|
229
|
+
const message = error instanceof OrchestrationStateError ? error.message : String(error);
|
|
230
|
+
return failResult('orchestration-evidence', `Invalid orchestration.json: ${message}`);
|
|
251
231
|
}
|
|
252
232
|
const policy = run.modelPolicy;
|
|
253
233
|
if (
|
|
@@ -269,30 +249,8 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
|
|
|
269
249
|
return failResult('orchestration-evidence', 'Run model policy source or recovery history is invalid');
|
|
270
250
|
}
|
|
271
251
|
for (const recovery of run.recoveryHistory) {
|
|
272
|
-
const validModelRecovery = recovery.code === 'MODEL_POLICY_SUPPLEMENTED'
|
|
273
|
-
&& recovery.previousSchemaVersion === 1
|
|
274
|
-
&& recovery.receiptCount === 0
|
|
275
|
-
&& recovery.pendingDelegation === false
|
|
276
|
-
&& exactText(recovery.recoveredAt)
|
|
277
|
-
&& recovery.policySource;
|
|
278
252
|
const guards = recovery.guards;
|
|
279
|
-
const
|
|
280
|
-
&& recovery.previousSchemaVersion === 2
|
|
281
|
-
&& recovery.previousStatus === 'paused'
|
|
282
|
-
&& recovery.previousPause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
|
|
283
|
-
&& recovery.client === 'codex'
|
|
284
|
-
&& recovery.resultingStatus === 'running'
|
|
285
|
-
&& exactText(recovery.recoveredAt)
|
|
286
|
-
&& guards?.stepCount === 0
|
|
287
|
-
&& guards?.nextStage === null
|
|
288
|
-
&& guards?.baselineEmpty === true
|
|
289
|
-
&& guards?.receiptCount === 0
|
|
290
|
-
&& guards?.pendingDelegation === false
|
|
291
|
-
&& guards?.commitAuthorizationUnused === true
|
|
292
|
-
&& guards?.completionEvidenceAbsent === true
|
|
293
|
-
&& guards?.commitIntentAbsent === true;
|
|
294
|
-
const validClaudeCodeRecovery = recovery.code === 'CLIENT_CAPABILITY_ENABLED_NO_MIGRATION'
|
|
295
|
-
&& recovery.previousSchemaVersion === 3
|
|
253
|
+
const validClaudeCodeRecovery = recovery.code === 'CLIENT_CAPABILITY_ENABLED'
|
|
296
254
|
&& recovery.previousStatus === 'paused'
|
|
297
255
|
&& recovery.previousPause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
|
|
298
256
|
&& recovery.client === 'claude-code'
|
|
@@ -304,9 +262,8 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
|
|
|
304
262
|
&& guards?.receiptCount === 0
|
|
305
263
|
&& guards?.pendingDelegation === false
|
|
306
264
|
&& guards?.commitAuthorizationUnused === true
|
|
307
|
-
&& guards?.completionEvidenceAbsent === true
|
|
308
|
-
|
|
309
|
-
if (!validModelRecovery && !validCapabilityRecovery && !validClaudeCodeRecovery) {
|
|
265
|
+
&& guards?.completionEvidenceAbsent === true;
|
|
266
|
+
if (!validClaudeCodeRecovery) {
|
|
310
267
|
return failResult('orchestration-evidence', 'Run recovery history contains invalid provenance');
|
|
311
268
|
}
|
|
312
269
|
}
|
|
@@ -376,12 +333,34 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
|
|
|
376
333
|
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has an unrelated reasoning-effort fallback reason`);
|
|
377
334
|
}
|
|
378
335
|
if (receipt.client === 'codex') {
|
|
336
|
+
const activatedCodex = ['activated', 'stage-completed', 'sealed', 'consumed'].includes(receipt.status);
|
|
379
337
|
const host = receipt.hostEvidence;
|
|
338
|
+
const provenance = receipt.lifecycleProvenance;
|
|
380
339
|
if (
|
|
381
|
-
|
|
382
|
-
||
|
|
383
|
-
|
|
384
|
-
|
|
340
|
+
!provenance
|
|
341
|
+
|| (activatedCodex && (
|
|
342
|
+
host?.kind !== 'codex-lifecycle-v2'
|
|
343
|
+
|| host.protocolVersion !== provenance.protocolVersion
|
|
344
|
+
|| host.hookDefinitionHash !== provenance.hookDefinitionHash
|
|
345
|
+
|| host.hookSource !== provenance.hookSource
|
|
346
|
+
|| host.hookSourcePathDigest !== provenance.hookSourcePathDigest
|
|
347
|
+
|| host.hookSourceHash !== provenance.hookSourceHash
|
|
348
|
+
|| host.capabilitySessionId !== provenance.capabilitySessionId
|
|
349
|
+
|| receipt.parentId !== provenance.capabilitySessionId
|
|
350
|
+
|| host.capabilityTurnId !== provenance.capabilityTurnId
|
|
351
|
+
|| host.controllerInstanceDigest !== provenance.controllerInstanceDigest
|
|
352
|
+
|| host.controlGeneration !== provenance.controlGeneration
|
|
353
|
+
|| host.spawnToolUseId === provenance.capabilityToolUseId
|
|
354
|
+
|| !exactText(host.spawnToolUseId)
|
|
355
|
+
|| !Number.isFinite(Date.parse(host.spawnObservedAt ?? ''))
|
|
356
|
+
|| !Number.isFinite(Date.parse(receipt.spawnDispatchedAt ?? ''))
|
|
357
|
+
|| !Number.isFinite(Date.parse(receipt.activationDeadlineAt ?? ''))
|
|
358
|
+
|| Date.parse(host.spawnObservedAt ?? '') < Date.parse(receipt.spawnDispatchedAt ?? '')
|
|
359
|
+
|| Date.parse(host.spawnObservedAt ?? '') > Date.parse(receipt.activationDeadlineAt ?? '')
|
|
360
|
+
|| !exactText(host.hookDefinitionHash)
|
|
361
|
+
|| !Number.isSafeInteger(host.startRevision)
|
|
362
|
+
|| host.startRevision < 1
|
|
363
|
+
))
|
|
385
364
|
) {
|
|
386
365
|
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid Codex start evidence`);
|
|
387
366
|
}
|
|
@@ -510,6 +489,63 @@ function checkTaskMeta({ taskDir, config }: any): any {
|
|
|
510
489
|
);
|
|
511
490
|
}
|
|
512
491
|
|
|
492
|
+
function checkRequiredPrDelivery({ taskDir, repositoryRoot, mode }: any): any {
|
|
493
|
+
const task = loadTask(taskDir);
|
|
494
|
+
if (!task.ok) return failResult('required-pr-delivery', task.message);
|
|
495
|
+
let projectConfig: any = {};
|
|
496
|
+
try {
|
|
497
|
+
const file = path.join(repositoryRoot, '.agents', '.airc.json');
|
|
498
|
+
if (safeStat(file)?.isFile()) projectConfig = JSON.parse(fs.readFileSync(file, 'utf8')) as any;
|
|
499
|
+
} catch {
|
|
500
|
+
return failResult('required-pr-delivery', 'Project configuration is invalid');
|
|
501
|
+
}
|
|
502
|
+
const flow = projectConfig.prFlow;
|
|
503
|
+
const status = String(task.metadata.pr_status || 'pending');
|
|
504
|
+
const prNumber = parsePrNumber(task.metadata.pr_number);
|
|
505
|
+
if (flow === 'disabled') return passResult('required-pr-delivery', 'Pull request delivery is disabled by project policy');
|
|
506
|
+
if (flow === 'required' && (status !== 'created' || prNumber === null)) {
|
|
507
|
+
return failResult('required-pr-delivery', 'Project policy requires a bound pull request before completion');
|
|
508
|
+
}
|
|
509
|
+
if (flow !== 'required' && status === 'pending') {
|
|
510
|
+
return failResult('required-pr-delivery', 'Pull request delivery is pending; create a PR or explicitly skip it');
|
|
511
|
+
}
|
|
512
|
+
if (status === 'created' && prNumber === null) {
|
|
513
|
+
return failResult('required-pr-delivery', 'Task claims a created pull request without a valid pr_number');
|
|
514
|
+
}
|
|
515
|
+
if (flow === 'required') {
|
|
516
|
+
if (mode === 'gate') {
|
|
517
|
+
return passResult('required-pr-delivery', 'Bound pull request shape is valid; merged-state inspection is reserved for hard preflight');
|
|
518
|
+
}
|
|
519
|
+
const inspected = inspectPlatformPullRequest(task.metadata.id, { cwd: repositoryRoot });
|
|
520
|
+
if (inspected.status === 'blocked') {
|
|
521
|
+
return blockedResult(
|
|
522
|
+
'required-pr-delivery',
|
|
523
|
+
inspected.error?.message || 'Unable to inspect the required pull request',
|
|
524
|
+
inspected.error?.code || 'PR_INSPECTION_BLOCKED'
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
const pullRequest = inspected.pullRequest;
|
|
528
|
+
const repository = inspected.platform.repository?.toLowerCase() || '';
|
|
529
|
+
const taskBranch = String(task.metadata.branch || '').trim();
|
|
530
|
+
if (inspected.status !== 'no-op' || !pullRequest || pullRequest.number !== prNumber) {
|
|
531
|
+
return failResult('required-pr-delivery', 'Bound pull request identity could not be verified');
|
|
532
|
+
}
|
|
533
|
+
if (
|
|
534
|
+
!repository
|
|
535
|
+
|| pullRequest.repository.toLowerCase() !== repository
|
|
536
|
+
|| pullRequest.base.repository.toLowerCase() !== repository
|
|
537
|
+
|| (taskBranch && pullRequest.head.ref !== taskBranch)
|
|
538
|
+
) {
|
|
539
|
+
return failResult('required-pr-delivery', 'Bound pull request identity does not match the task');
|
|
540
|
+
}
|
|
541
|
+
if (pullRequest.state !== 'closed' || !pullRequest.mergedAt || !pullRequest.mergeCommitSha) {
|
|
542
|
+
return failResult('required-pr-delivery', 'Project policy requires the bound pull request to be merged');
|
|
543
|
+
}
|
|
544
|
+
return passResult('required-pr-delivery', `Pull request delivery policy satisfied (merged PR #${prNumber})`);
|
|
545
|
+
}
|
|
546
|
+
return passResult('required-pr-delivery', `Pull request delivery policy satisfied (${status})`);
|
|
547
|
+
}
|
|
548
|
+
|
|
513
549
|
function validateTaskBranch(metadata: any): any {
|
|
514
550
|
if (isBlank(metadata.branch)) {
|
|
515
551
|
return null;
|
|
@@ -1600,7 +1636,14 @@ function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks: reque
|
|
|
1600
1636
|
const checks = [];
|
|
1601
1637
|
for (const [type, checkConfig] of Object.entries(verifyConfig.checks || {})) {
|
|
1602
1638
|
if (checkConfig === null) continue;
|
|
1603
|
-
const result = runCheck(type, {
|
|
1639
|
+
const result = runCheck(type, {
|
|
1640
|
+
mode,
|
|
1641
|
+
skillName,
|
|
1642
|
+
taskDir: path.resolve(taskDir),
|
|
1643
|
+
artifactFile,
|
|
1644
|
+
repositoryRoot: repoRoot,
|
|
1645
|
+
config: checkConfig
|
|
1646
|
+
}, shared);
|
|
1604
1647
|
checks.push(result);
|
|
1605
1648
|
if (result.status === "blocked") break;
|
|
1606
1649
|
}
|
|
@@ -1611,7 +1654,16 @@ function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks: reque
|
|
|
1611
1654
|
const config = (verifyConfig.checks || {})[type];
|
|
1612
1655
|
if (config === undefined) return { skill: skillName, ...failResult(type, `Unknown check type '${type}' for skill '${skillName}'.`) };
|
|
1613
1656
|
if (config === null) return { skill: skillName, ...passResult(type, `Check '${type}' is disabled for skill '${skillName}'.`) };
|
|
1614
|
-
return {
|
|
1657
|
+
return {
|
|
1658
|
+
skill: skillName,
|
|
1659
|
+
...runCheck(type, {
|
|
1660
|
+
skillName,
|
|
1661
|
+
taskDir: path.resolve(taskDir),
|
|
1662
|
+
artifactFile,
|
|
1663
|
+
repositoryRoot: repoRoot,
|
|
1664
|
+
config
|
|
1665
|
+
}, shared)
|
|
1666
|
+
};
|
|
1615
1667
|
}
|
|
1616
1668
|
|
|
1617
1669
|
export { verifyInProcess };
|
package/lib/task/verification.ts
CHANGED
|
@@ -15,7 +15,7 @@ type VerificationEvent =
|
|
|
15
15
|
| 'review-analysis.completed' | 'plan.completed' | 'review-plan.completed'
|
|
16
16
|
| 'code.completed' | 'review-code.completed' | 'manual-validation.completed' | 'validation-run.completed'
|
|
17
17
|
| 'block-task.completed' | 'cancel-task.completed' | 'commit.completed'
|
|
18
|
-
| 'complete-task.preflight' | 'complete-task.completed'
|
|
18
|
+
| 'complete-task.preflight' | 'complete-task.hard-preflight' | 'complete-task.completed'
|
|
19
19
|
| 'create-pr.completed' | 'create-task.completed'
|
|
20
20
|
| 'import-codescan.completed' | 'import-dependabot.completed'
|
|
21
21
|
| 'import-issue.completed' | 'watch-pr.completed'
|
|
@@ -69,7 +69,8 @@ const VERIFICATION_CATALOG: Readonly<Record<VerificationEvent, VerificationSpec>
|
|
|
69
69
|
'block-task.completed': gate('block-task', 'blocked'),
|
|
70
70
|
'cancel-task.completed': gate('cancel-task', 'completed'),
|
|
71
71
|
'commit.completed': gate('commit', 'active'),
|
|
72
|
-
'complete-task.preflight': { skill: 'complete-task', expectedState: 'active', mode: 'checks', checks: ['
|
|
72
|
+
'complete-task.preflight': { skill: 'complete-task', expectedState: 'active', mode: 'checks', checks: ['required-pr-delivery'] },
|
|
73
|
+
'complete-task.hard-preflight': { skill: 'complete-task', expectedState: 'active', mode: 'checks', checks: ['required-pr-delivery'] },
|
|
73
74
|
'complete-task.completed': gate('complete-task', 'completed'),
|
|
74
75
|
'create-pr.completed': gate('create-pr', 'active'),
|
|
75
76
|
'create-task.completed': gate('create-task', 'active'),
|
|
@@ -12,6 +12,7 @@ import type { WorkflowWarning } from './workflow-warnings.ts';
|
|
|
12
12
|
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
13
13
|
import type { TaskMutation, TaskOperationSummary, TaskWriteOptions } from './write.ts';
|
|
14
14
|
import { allowsManualOverride } from './guard-override.ts';
|
|
15
|
+
import { retryHintForWarning, type OperationWarning } from './operation-outcome.ts';
|
|
15
16
|
|
|
16
17
|
type WarningSeverity = 'IMPORTANT' | 'ACTION_REQUIRED';
|
|
17
18
|
type WarningStatus = 'open' | 'resolved' | 'ignored';
|
|
@@ -28,6 +29,12 @@ type WorkflowWarningIntentResult = {
|
|
|
28
29
|
error: { code: string; message: string } | null;
|
|
29
30
|
};
|
|
30
31
|
|
|
32
|
+
type FinalizationWarningProjectionResult = Readonly<{
|
|
33
|
+
status: 'applied' | 'no-op' | 'failed';
|
|
34
|
+
changed: boolean;
|
|
35
|
+
error: { code: string; message: string } | null;
|
|
36
|
+
}>;
|
|
37
|
+
|
|
31
38
|
function failed(intent: WorkflowWarningIntent, code: string, message: string, taskId: string | null = null, entityId: string | null = null): WorkflowWarningIntentResult {
|
|
32
39
|
return { status: 'failed', changed: false, intent: intent.kind, taskId, entityId, before: null, after: null, warnings: [], operations: [], error: { code, message } };
|
|
33
40
|
}
|
|
@@ -140,5 +147,53 @@ function applyWorkflowWarningIntent(intent: WorkflowWarningIntent, options: Task
|
|
|
140
147
|
};
|
|
141
148
|
}
|
|
142
149
|
|
|
143
|
-
|
|
144
|
-
|
|
150
|
+
function projectFinalizationWarning(
|
|
151
|
+
taskRef: string,
|
|
152
|
+
warning: OperationWarning & { status: 'open' | 'resolved'; resolvedAt: string | null },
|
|
153
|
+
options: TaskWriteOptions = {}
|
|
154
|
+
): FinalizationWarningProjectionResult {
|
|
155
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
156
|
+
if (!resolved.ok) return { status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } };
|
|
157
|
+
if (resolved.state !== 'completed') return { status: 'failed', changed: false, error: { code: 'TASK_STATE_MISMATCH', message: `task ${resolved.taskId} is ${resolved.state}, expected completed` } };
|
|
158
|
+
let content: string;
|
|
159
|
+
let rows: WorkflowWarning[];
|
|
160
|
+
try {
|
|
161
|
+
content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
162
|
+
rows = parseWorkflowWarnings(content);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
return { status: 'failed', changed: false, error: { code: 'WARNING_DOCUMENT_INVALID', message: error instanceof Error ? error.message : String(error) } };
|
|
165
|
+
}
|
|
166
|
+
const metadata = (options.metadataProvider ?? captureTaskWriteMetadata)();
|
|
167
|
+
const existing = rows.find((row) => row.step === warning.step && row.code === warning.code && row.target === warning.target);
|
|
168
|
+
const after: WorkflowWarning = existing
|
|
169
|
+
? {
|
|
170
|
+
...existing,
|
|
171
|
+
severity: warning.severity,
|
|
172
|
+
status: warning.status,
|
|
173
|
+
message: warning.message,
|
|
174
|
+
action: retryHintForWarning(warning),
|
|
175
|
+
resolvedAt: warning.resolvedAt || (warning.status === 'resolved' ? metadata.timestamp : ''),
|
|
176
|
+
resolution: warning.status === 'resolved' ? 'Resolved by a successful finalization retry' : ''
|
|
177
|
+
}
|
|
178
|
+
: {
|
|
179
|
+
id: nextWarningId(rows), time: metadata.timestamp, step: warning.step,
|
|
180
|
+
severity: warning.severity, code: warning.code, status: warning.status,
|
|
181
|
+
target: warning.target, message: warning.message, action: retryHintForWarning(warning),
|
|
182
|
+
resolvedAt: warning.resolvedAt || '', resolution: warning.status === 'resolved' ? 'Resolved by a successful finalization retry' : ''
|
|
183
|
+
};
|
|
184
|
+
if (existing && JSON.stringify(existing) === JSON.stringify(after)) return { status: 'no-op', changed: false, error: null };
|
|
185
|
+
const written = writeTask({
|
|
186
|
+
taskRef: resolved.taskId,
|
|
187
|
+
expectedState: 'completed',
|
|
188
|
+
mutations: [...sectionMutation(content), rowMutation(after)]
|
|
189
|
+
}, {
|
|
190
|
+
...options,
|
|
191
|
+
taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: 'completed' },
|
|
192
|
+
metadataProvider: () => metadata
|
|
193
|
+
});
|
|
194
|
+
if (written.status === 'failed') return { status: 'failed', changed: false, error: { code: written.error.code, message: written.error.message } };
|
|
195
|
+
return { status: written.status === 'applied' ? 'applied' : 'no-op', changed: written.changed, error: null };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export { applyWorkflowWarningIntent, projectFinalizationWarning };
|
|
199
|
+
export type { FinalizationWarningProjectionResult, WarningSeverity, WarningStatus, WorkflowWarningIntent, WorkflowWarningIntentResult };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fitlab-ai/agent-infra",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.10",
|
|
4
4
|
"description": "Bootstrap tool for AI multi-tool collaboration infrastructure — works with Claude Code, Codex, Antigravity CLI, and OpenCode",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"yaml": "^2.9.0"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
|
-
"build": "
|
|
57
|
+
"build": "node scripts/run-build.js",
|
|
58
58
|
"demo:regen": "sh scripts/demo-regen.sh",
|
|
59
59
|
"prepare": "git config core.hooksPath .git-hooks || true",
|
|
60
60
|
"typecheck": "tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.jschecks.json --noEmit",
|
|
@@ -20,18 +20,17 @@ This makes Git invoke the hooks in the project repository's `.git-hooks/` direct
|
|
|
20
20
|
|
|
21
21
|
## Development Checkout CLI Alignment
|
|
22
22
|
|
|
23
|
-
When developing agent-infra from a source checkout, install dependencies and rebuild
|
|
23
|
+
When developing the agent-infra CLI from a source checkout, install dependencies and rebuild it as needed:
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
npm install
|
|
27
27
|
npm run build
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
Lifecycle hooks
|
|
30
|
+
Lifecycle hooks always invoke `agent-infra-internal` through `PATH`; they do not implicitly switch to checkout source or `dist`. In a Codex sandbox, the controller's `PATH`-first shim is authoritative and binds the controller's selected executable. Skills, rules, hooks, and task data continue to come from the current checkout. The commands above develop the CLI itself and do not change hook executable selection. To run the CLI from the current checkout, invoke the build output directly and compare its version with the package version. Do not run `npm link` on the host: it writes to npm's global prefix and can shadow a stable installation managed by Homebrew or npm.
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
|
|
34
|
-
ai version --raw
|
|
33
|
+
node ./dist/bin/cli.js version --raw
|
|
35
34
|
node -p "'v' + require('./package.json').version"
|
|
36
35
|
```
|
|
37
36
|
|
|
@@ -20,18 +20,17 @@ git config core.hooksPath .git-hooks
|
|
|
20
20
|
|
|
21
21
|
## 开发检出中的 CLI 对齐
|
|
22
22
|
|
|
23
|
-
从源码检出开发 agent-infra
|
|
23
|
+
从源码检出开发 agent-infra CLI 时,请按需安装依赖并重新构建 CLI:
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
npm install
|
|
27
27
|
npm run build
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
生命周期 hook 始终通过 `PATH` 调用 `agent-infra-internal`,不会隐式切换到当前检出的 source 或 `dist`。在 Codex sandbox 中,controller 放在 `PATH` 首位的 shim 是权威入口,并绑定 controller 选定的 executable。skills、rules、hooks 和任务数据仍从当前检出读取。上面的命令只用于开发 CLI 本身,不会改变 hook 的 executable 选择。如需运行当前检出的 CLI,请直接执行构建产物并比较当前版本与包版本。不要在宿主机上执行 `npm link`:它会写入 npm 全局前缀,并可能遮蔽由 Homebrew 或 npm 安装的稳定版。
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
|
|
34
|
-
ai version --raw
|
|
33
|
+
node ./dist/bin/cli.js version --raw
|
|
35
34
|
node -p "'v' + require('./package.json').version"
|
|
36
35
|
```
|
|
37
36
|
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import { existsSync } from 'node:fs';
|
|
4
3
|
import { readFileSync } from 'node:fs';
|
|
5
4
|
import { fileURLToPath } from 'node:url';
|
|
6
5
|
|
|
7
6
|
const MAX_INPUT_BYTES = 64 * 1024;
|
|
8
|
-
const repoPackageJson = fileURLToPath(new URL('../../package.json', import.meta.url));
|
|
9
|
-
const sourceInternalCli = fileURLToPath(new URL('../../bin/internal-cli.ts', import.meta.url));
|
|
10
|
-
const localInternalCli = fileURLToPath(new URL('../../dist/bin/internal-cli.js', import.meta.url));
|
|
11
7
|
const codexHooks = fileURLToPath(new URL('../../.codex/hooks.json', import.meta.url));
|
|
12
8
|
const clientIndex = process.argv.indexOf('--client');
|
|
13
9
|
const client = clientIndex >= 0 ? process.argv[clientIndex + 1] : '';
|
|
@@ -17,22 +13,6 @@ if (client !== 'claude-code' && client !== 'codex') {
|
|
|
17
13
|
}
|
|
18
14
|
|
|
19
15
|
function internalCliInvocation(args) {
|
|
20
|
-
let isAgentInfraSourceCheckout = false;
|
|
21
|
-
try {
|
|
22
|
-
isAgentInfraSourceCheckout = JSON.parse(readFileSync(repoPackageJson, 'utf8')).name === '@fitlab-ai/agent-infra';
|
|
23
|
-
} catch {
|
|
24
|
-
// Installed project hooks do not require a repository package.json.
|
|
25
|
-
}
|
|
26
|
-
if (isAgentInfraSourceCheckout && existsSync(sourceInternalCli)) {
|
|
27
|
-
return {
|
|
28
|
-
command: process.execPath,
|
|
29
|
-
commandArgs: ['--experimental-strip-types', sourceInternalCli, ...args],
|
|
30
|
-
shell: false
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
if (existsSync(localInternalCli)) {
|
|
34
|
-
return { command: process.execPath, commandArgs: [localInternalCli, ...args], shell: false };
|
|
35
|
-
}
|
|
36
16
|
return { command: 'agent-infra-internal', commandArgs: args, shell: process.platform === 'win32' };
|
|
37
17
|
}
|
|
38
18
|
|
|
@@ -125,7 +105,7 @@ process.stdin.on('end', () => {
|
|
|
125
105
|
input: JSON.stringify(normalized),
|
|
126
106
|
shell,
|
|
127
107
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
128
|
-
timeout:
|
|
108
|
+
timeout: 30_000
|
|
129
109
|
});
|
|
130
110
|
process.stdout.write(output);
|
|
131
111
|
} catch (error) {
|
|
@@ -12,6 +12,7 @@ so you can quickly find "which ones to read" without opening each file.
|
|
|
12
12
|
- [`next-step-output.md`](next-step-output.md) — "Next step" output rules: task short-id rendering and the `Completed at` trailer.
|
|
13
13
|
- [`version-stamp.md`](version-stamp.md) — How and when to stamp `agent_infra_version`.
|
|
14
14
|
- [`debugging-guide.md`](debugging-guide.md) — Structured debugging flow: gather evidence → form hypothesis → verify hypothesis → fix the root cause; no blind patch-and-retry.
|
|
15
|
+
- [`compatibility-policy.md`](compatibility-policy.md) — Compatibility by exception: admission evidence, implementation boundaries, exit conditions, and lifecycle review requirements.
|
|
15
16
|
|
|
16
17
|
## Issue / PR
|
|
17
18
|
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
- [`next-step-output.md`](next-step-output.md) — 「下一步」输出规则:任务短号渲染与 `Completed at` 收尾行。
|
|
12
12
|
- [`version-stamp.md`](version-stamp.md) — `agent_infra_version` 版本戳的取值命令与写入时机。
|
|
13
13
|
- [`debugging-guide.md`](debugging-guide.md) — 结构化调试流程:收集证据→形成假设→验证假设→修复根因,禁止盲目改代码重试。
|
|
14
|
+
- [`compatibility-policy.md`](compatibility-policy.md) — 兼容性默认关闭:准入证据、实现边界、退出条件与生命周期审查要求。
|
|
14
15
|
|
|
15
16
|
## Issue / PR
|
|
16
17
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Compatibility Policy
|
|
2
|
+
|
|
3
|
+
This rule governs compatibility decisions during analysis, planning, implementation, and review. Support the current contract by default. Compatibility is an exception budget that requires evidence and an exit condition; it is not a synonym for safety.
|
|
4
|
+
|
|
5
|
+
## What Counts as Compatibility
|
|
6
|
+
|
|
7
|
+
Compatibility logic includes accepting or writing multiple generations of schemas, fields, states, or commands; retaining legacy aliases, adapters, wrappers, or shims; migrating data in the normal runtime path; falling back to different semantics based on an old format; or maintaining parallel facts and tests for old callers.
|
|
8
|
+
|
|
9
|
+
Current-operation idempotency and crash recovery, explicit platform or permission capability branches, fail-closed handling of unknown state, and user-required public multi-version contracts are not inherently legacy compatibility. Do not use those labels to expand support for an old protocol.
|
|
10
|
+
|
|
11
|
+
## Admission Gate
|
|
12
|
+
|
|
13
|
+
Add or extend compatibility only when the task or an existing public contract explicitly requires it and the artifact records all four items:
|
|
14
|
+
|
|
15
|
+
1. **Subject**: concrete consumers, released versions, or stored data still using the old contract.
|
|
16
|
+
2. **Necessity**: why an atomic cutover, one-time conversion, or actionable failure is insufficient.
|
|
17
|
+
3. **Window**: a version, date, or verifiable endpoint for the compatibility period.
|
|
18
|
+
4. **Exit**: removal conditions, ownership, and the code, tests, and documentation to delete.
|
|
19
|
+
|
|
20
|
+
If any item is missing, compatibility is unauthorized scope expansion. Implement the current contract, fail closed with an actionable repair message for old input, or perform an explicit one-time conversion outside the main path.
|
|
21
|
+
|
|
22
|
+
## Implementation Boundaries
|
|
23
|
+
|
|
24
|
+
- Keep one canonical write format and one source of truth. Do not dual-write, maintain shadow state, or try the new path before silently falling back to the old path.
|
|
25
|
+
- Concentrate an approved conversion at one boundary and immediately enter the current model; business logic must not understand several historical versions.
|
|
26
|
+
- Use warnings only when the primary action remains correct and the user can retry an ancillary operation. Never hide uncertainty about identity, authorization, data interpretation, or side-effect correctness.
|
|
27
|
+
- When removing an old capability, remove its adapters, branches, fixtures, tests, and migration documentation together. Do not add negative tests that permanently remember deleted behavior.
|
|
28
|
+
- Never guess the meaning of unknown state. Preserve it, stop writes, and provide rebuild or manual-repair guidance.
|
|
29
|
+
|
|
30
|
+
## Lifecycle Requirements
|
|
31
|
+
|
|
32
|
+
- **Analysis**: separate explicit compatibility requirements from model speculation and identify real consumers or stored data. State current-only when evidence is absent.
|
|
33
|
+
- **Plan**: for approved compatibility, identify the boundary, single conversion point, window, and deletion plan. Do not present a compatibility layer as the default low-risk option.
|
|
34
|
+
- **Implementation**: implement only the approved compatibility budget. Do not preserve an unapproved old branch “just in case.”
|
|
35
|
+
- **Review**: verify the four admission items, single source of truth, and exit condition. Flag unauthorized compatibility, indefinite shims, dual writes, and runtime-path migrations as scope and maintainability problems.
|
|
36
|
+
|
|
37
|
+
## Existing Compatibility Logic
|
|
38
|
+
|
|
39
|
+
During an ordinary task, record out-of-scope compatibility logic and its risk without deleting it. A dedicated simplification task should first inventory consumers and stored data, then process items in this order: remove now, warn then remove, retain temporarily.
|