@fitlab-ai/agent-infra 0.9.1 → 0.9.2
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 +16 -4
- package/README.zh-CN.md +16 -4
- package/bin/cli.ts +12 -0
- package/dist/bin/cli.js +14 -0
- package/dist/bin/internal-cli.js +15 -0
- package/dist/lib/agent-client.js +163 -0
- package/dist/lib/agent-clients/adapter.js +294 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +62 -0
- package/dist/lib/agent-clients/adapters/codex-sandbox.js +210 -0
- package/dist/lib/agent-clients/adapters/codex.js +54 -0
- package/dist/lib/agent-clients/adapters/gemini-cli.js +64 -0
- package/dist/lib/agent-clients/adapters/index.js +20 -0
- package/dist/lib/agent-clients/adapters/opencode.js +64 -0
- package/dist/lib/agent-clients/config.js +177 -0
- package/dist/lib/agent-clients/custom-tuis.js +99 -0
- package/dist/lib/agent-clients/invocation.js +9 -0
- package/dist/lib/agent-clients/next-steps.js +38 -0
- package/dist/lib/agent-clients/project-assets.js +45 -0
- package/dist/lib/agent-clients/reconcile.js +235 -0
- package/dist/lib/agent-clients/registry.js +72 -0
- package/dist/lib/agent-clients/schema.js +57 -0
- package/dist/lib/agent-clients/types.js +29 -0
- package/dist/lib/builtin-tuis.js +9 -14
- package/dist/lib/defaults.json +85 -73
- package/dist/lib/git/review-snapshot.js +25 -10
- package/dist/lib/git/reviewed-commit-equivalence.js +110 -0
- package/dist/lib/init.js +47 -91
- package/dist/lib/internal/agent-client.js +117 -0
- package/dist/lib/internal/git-workflow.js +1 -1
- package/dist/lib/internal/release-workflow.js +71 -16
- package/dist/lib/internal/task-orchestration.js +151 -0
- package/dist/lib/internal/task-review.js +65 -0
- package/dist/lib/merge.js +2 -1
- package/dist/lib/platform/adapters.js +38 -1
- package/dist/lib/platform/change-request-git-evidence.js +113 -0
- package/dist/lib/platform/merged-pr-equivalence.js +41 -11
- package/dist/lib/platform/pr-checks.js +38 -10
- package/dist/lib/platform/pull-requests.js +110 -6
- package/dist/lib/platform/releases.js +1 -1
- package/dist/lib/platform/verification-required.js +8 -10
- package/dist/lib/prompt.js +15 -3
- package/dist/lib/release/channels.js +4 -1
- package/dist/lib/release/workflow.js +4 -2
- package/dist/lib/remove-dir.js +25 -0
- package/dist/lib/run/tui.js +8 -2
- package/dist/lib/sandbox/agent-client-reconciler.js +234 -0
- package/dist/lib/sandbox/clipboard/linux.js +2 -1
- package/dist/lib/sandbox/commands/create.js +76 -180
- package/dist/lib/sandbox/commands/enter.js +17 -1
- package/dist/lib/sandbox/commands/prune.js +2 -2
- package/dist/lib/sandbox/commands/rm.js +3 -2
- package/dist/lib/sandbox/config.js +21 -4
- package/dist/lib/sandbox/constants.js +3 -0
- package/dist/lib/sandbox/dockerfile.js +2 -1
- package/dist/lib/sandbox/managed-fs.js +10 -5
- package/dist/lib/sandbox/recovery.js +52 -36
- package/dist/lib/sandbox/shell.js +1 -1
- package/dist/lib/sandbox/tool-types.js +8 -0
- package/dist/lib/sandbox/tools.js +2 -126
- package/dist/lib/task/artifact-lifecycle.js +1 -49
- package/dist/lib/task/commands/decisions.js +50 -20
- package/dist/lib/task/delegation-receipts.js +107 -0
- package/dist/lib/task/events.js +100 -1
- package/dist/lib/task/orchestration.js +420 -0
- package/dist/lib/task/review-artifacts.js +143 -59
- package/dist/lib/task/review-finalization.js +166 -0
- package/dist/lib/task/review-fingerprint.js +4 -0
- package/dist/lib/task/verification-checks.js +2 -1
- package/dist/lib/task/verification-engine.js +156 -28
- package/dist/lib/task/verification-review.js +1 -1
- package/dist/lib/task/verification.js +3 -1
- package/dist/lib/task/workspace-snapshot.js +34 -0
- package/dist/lib/update.js +42 -161
- package/lib/agent-client.ts +214 -0
- package/lib/agent-clients/adapter.ts +461 -0
- package/lib/agent-clients/adapters/claude-code.ts +70 -0
- package/lib/agent-clients/adapters/codex-sandbox.ts +245 -0
- package/lib/agent-clients/adapters/codex.ts +58 -0
- package/lib/agent-clients/adapters/gemini-cli.ts +65 -0
- package/lib/agent-clients/adapters/index.ts +27 -0
- package/lib/agent-clients/adapters/opencode.ts +66 -0
- package/lib/agent-clients/config.ts +278 -0
- package/lib/agent-clients/custom-tuis.ts +149 -0
- package/lib/agent-clients/invocation.ts +19 -0
- package/lib/agent-clients/next-steps.ts +72 -0
- package/lib/agent-clients/project-assets.ts +96 -0
- package/lib/agent-clients/reconcile.ts +324 -0
- package/lib/agent-clients/registry.ts +144 -0
- package/lib/agent-clients/schema.ts +62 -0
- package/lib/agent-clients/types.ts +78 -0
- package/lib/benchmark/schemas/v1.0.0/case-manifest.schema.json +120 -0
- package/lib/benchmark/schemas/v1.0.0/common.schema.json +109 -0
- package/lib/benchmark/schemas/v1.0.0/grader-result.schema.json +160 -0
- package/lib/benchmark/schemas/v1.0.0/run-manifest.schema.json +122 -0
- package/lib/benchmark/schemas/v1.0.0/subject.schema.json +227 -0
- package/lib/builtin-tuis.ts +16 -15
- package/lib/defaults.json +24 -12
- package/lib/git/review-snapshot.ts +39 -9
- package/lib/git/reviewed-commit-equivalence.ts +135 -0
- package/lib/init.ts +53 -121
- package/lib/internal/agent-client.ts +131 -0
- package/lib/internal/git-workflow.ts +1 -1
- package/lib/internal/release-workflow.ts +68 -16
- package/lib/internal/task-orchestration.ts +132 -0
- package/lib/internal/task-review.ts +65 -0
- package/lib/merge.ts +2 -1
- package/lib/platform/adapters.ts +142 -3
- package/lib/platform/change-request-git-evidence.ts +159 -0
- package/lib/platform/merged-pr-equivalence.ts +51 -14
- package/lib/platform/pr-checks.ts +50 -22
- package/lib/platform/pull-requests.ts +134 -25
- package/lib/platform/releases.ts +1 -1
- package/lib/platform/verification-required.ts +12 -10
- package/lib/prompt.ts +16 -3
- package/lib/release/channels.ts +4 -1
- package/lib/release/workflow.ts +4 -3
- package/lib/remove-dir.ts +28 -0
- package/lib/run/tui.ts +9 -1
- package/lib/sandbox/agent-client-reconciler.ts +378 -0
- package/lib/sandbox/clipboard/linux.ts +2 -1
- package/lib/sandbox/commands/create.ts +111 -192
- package/lib/sandbox/commands/enter.ts +23 -1
- package/lib/sandbox/commands/prune.ts +2 -2
- package/lib/sandbox/commands/rm.ts +3 -2
- package/lib/sandbox/config.ts +31 -4
- package/lib/sandbox/constants.ts +6 -0
- package/lib/sandbox/dockerfile.ts +2 -1
- package/lib/sandbox/managed-fs.ts +18 -5
- package/lib/sandbox/recovery.ts +98 -67
- package/lib/sandbox/shell.ts +1 -1
- package/lib/sandbox/tool-types.ts +143 -0
- package/lib/sandbox/tools.ts +5 -157
- package/lib/task/artifact-lifecycle.ts +1 -38
- package/lib/task/commands/decisions.ts +53 -20
- package/lib/task/delegation-receipts.ts +170 -0
- package/lib/task/events.ts +110 -2
- package/lib/task/orchestration.ts +543 -0
- package/lib/task/review-artifacts.ts +201 -69
- package/lib/task/review-finalization.ts +275 -0
- package/lib/task/review-fingerprint.ts +5 -0
- package/lib/task/verification-checks.ts +2 -1
- package/lib/task/verification-engine.ts +177 -28
- package/lib/task/verification-review.ts +1 -0
- package/lib/task/verification.ts +5 -2
- package/lib/task/workspace-snapshot.ts +36 -0
- package/lib/update.ts +48 -184
- package/package.json +2 -2
- package/templates/.agents/README.en.md +21 -20
- package/templates/.agents/README.zh-CN.md +21 -20
- package/templates/.agents/hooks/lifecycle-delegation.js +64 -0
- package/templates/.agents/rules/README.en.md +2 -0
- package/templates/.agents/rules/README.zh-CN.md +2 -0
- package/templates/.agents/rules/commit-and-pr.en.md +1 -0
- package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -0
- package/templates/.agents/rules/human-decision-context.en.md +14 -12
- package/templates/.agents/rules/human-decision-context.zh-CN.md +14 -12
- package/templates/.agents/rules/lifecycle-orchestration.en.md +21 -0
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +21 -0
- package/templates/.agents/rules/next-step-output.en.md +16 -0
- package/templates/.agents/rules/next-step-output.zh-CN.md +16 -0
- package/templates/.agents/rules/no-mid-flow-questions.en.md +2 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +2 -0
- package/templates/.agents/rules/release-commands.github.en.md +11 -5
- package/templates/.agents/rules/release-commands.github.zh-CN.md +11 -5
- package/templates/.agents/rules/review-handshake.en.md +4 -4
- package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
- package/templates/.agents/rules/review-method.en.md +113 -0
- package/templates/.agents/rules/review-method.zh-CN.md +98 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/check-task/SKILL.en.md +16 -16
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -16
- package/templates/.agents/skills/close-codescan/SKILL.en.md +4 -4
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +4 -4
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +14 -16
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +14 -16
- package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
- package/templates/.agents/skills/commit/SKILL.en.md +11 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +11 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +24 -28
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +24 -28
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.en.md +10 -10
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +10 -10
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
- package/templates/.agents/skills/create-task/SKILL.en.md +11 -11
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +11 -11
- package/templates/.agents/skills/import-codescan/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/init-labels/SKILL.en.md +4 -4
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/init-milestones/SKILL.en.md +4 -4
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
- package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -3
- package/templates/.agents/skills/review-analysis/config/verify.en.json +6 -0
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +6 -0
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +13 -15
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +13 -15
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +51 -5
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +52 -6
- package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +25 -22
- package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +25 -22
- package/templates/.agents/skills/review-code/SKILL.en.md +28 -8
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +28 -8
- package/templates/.agents/skills/review-code/config/verify.en.json +7 -0
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +7 -0
- package/templates/.agents/skills/review-code/reference/concurrency-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/concurrency-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-code/reference/cross-platform-risks.en.md +27 -0
- package/templates/.agents/skills/review-code/reference/cross-platform-risks.zh-CN.md +27 -0
- package/templates/.agents/skills/review-code/reference/documentation-antipatterns.en.md +13 -0
- package/templates/.agents/skills/review-code/reference/documentation-antipatterns.zh-CN.md +13 -0
- package/templates/.agents/skills/review-code/reference/migration-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +22 -24
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +22 -24
- package/templates/.agents/skills/review-code/reference/report-template.en.md +54 -7
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +54 -7
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +29 -24
- package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +29 -24
- package/templates/.agents/skills/review-code/reference/security-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/security-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-plan/SKILL.en.md +13 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -3
- package/templates/.agents/skills/review-plan/config/verify.en.json +6 -0
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +6 -0
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +13 -15
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +13 -15
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +61 -5
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +61 -5
- package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +21 -24
- package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +21 -24
- package/templates/.agents/skills/run-task/SKILL.en.md +22 -0
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +22 -0
- package/templates/.agents/skills/run-task/config/verify.en.json +9 -0
- package/templates/.agents/skills/run-task/config/verify.zh-CN.json +9 -0
- package/templates/.agents/skills/test/SKILL.en.md +4 -4
- package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/test-integration/SKILL.en.md +4 -4
- package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -5
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +647 -203
- package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +4 -4
- package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/watch-pr/SKILL.en.md +8 -8
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +8 -8
- package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +6 -0
- package/templates/.claude/commands/run-task.en.md +8 -0
- package/templates/.claude/commands/run-task.zh-CN.md +8 -0
- package/templates/.claude/settings.json +24 -0
- package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +3 -0
- package/templates/.gemini/commands/_project_/run-task.en.toml +8 -0
- package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +8 -0
- package/templates/.opencode/commands/run-task.en.md +11 -0
- package/templates/.opencode/commands/run-task.zh-CN.md +11 -0
package/lib/init.ts
CHANGED
|
@@ -8,13 +8,13 @@ import { resolveTemplateDir } from './paths.ts';
|
|
|
8
8
|
import { renderFile, copySkillDir, KNOWN_PLATFORMS } from './render.ts';
|
|
9
9
|
import { enginesForPlatform } from './sandbox/engines/index.ts';
|
|
10
10
|
import { VERSION } from './version.ts';
|
|
11
|
+
import { listAgentClientAdapters } from './agent-clients/registry.ts';
|
|
12
|
+
import { serializeAgentClients } from './agent-clients/config.ts';
|
|
11
13
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} from './builtin-tuis.ts';
|
|
17
|
-
import type { BuiltinTUIId } from './builtin-tuis.ts';
|
|
14
|
+
applyAgentClientReconciliation,
|
|
15
|
+
planAgentClientReconciliation
|
|
16
|
+
} from './agent-clients/reconcile.ts';
|
|
17
|
+
import type { AgentClientState } from './agent-clients/types.ts';
|
|
18
18
|
|
|
19
19
|
type FileRegistry = {
|
|
20
20
|
managed: string[];
|
|
@@ -28,6 +28,7 @@ type SourceEntry = {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
type Defaults = {
|
|
31
|
+
agentClients: AgentConfig['agentClients'];
|
|
31
32
|
files: FileRegistry;
|
|
32
33
|
sandbox: Record<string, unknown>;
|
|
33
34
|
task: { shortIdLength: number };
|
|
@@ -44,7 +45,7 @@ type AgentConfig = {
|
|
|
44
45
|
task: { shortIdLength: number };
|
|
45
46
|
labels: Record<string, unknown>;
|
|
46
47
|
files: FileRegistry;
|
|
47
|
-
|
|
48
|
+
agentClients: ReturnType<typeof serializeAgentClients>;
|
|
48
49
|
templates?: { sources: SourceEntry[] };
|
|
49
50
|
skills?: { sources: SourceEntry[] };
|
|
50
51
|
};
|
|
@@ -59,28 +60,6 @@ const PLATFORM_DEFAULT_ENGINES = Object.freeze({
|
|
|
59
60
|
win32: 'wsl2'
|
|
60
61
|
});
|
|
61
62
|
|
|
62
|
-
function isPathOwnedByOtherPlatform(relativePath: string, platformType: string): boolean {
|
|
63
|
-
const top = String(relativePath || '').replace(/\\/g, '/').replace(/^\.\//, '').split('/')[0] ?? '';
|
|
64
|
-
if (!top.startsWith('.')) return false;
|
|
65
|
-
|
|
66
|
-
const candidate = top.slice(1);
|
|
67
|
-
if (!KNOWN_PLATFORMS.has(candidate)) return false;
|
|
68
|
-
return candidate !== platformType;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function buildDefaultFiles(platformType: string, enabledTUIs: Set<BuiltinTUIId>): FileRegistry {
|
|
72
|
-
const ownedByDisabled = (entry: string) => isPathOwnedByDisabledTUI(entry, enabledTUIs);
|
|
73
|
-
return {
|
|
74
|
-
managed: (defaults.files.managed || []).filter(
|
|
75
|
-
(entry) => !isPathOwnedByOtherPlatform(entry, platformType) && !ownedByDisabled(entry)
|
|
76
|
-
),
|
|
77
|
-
merged: (defaults.files.merged || []).filter(
|
|
78
|
-
(entry) => !isPathOwnedByOtherPlatform(entry, platformType) && !ownedByDisabled(entry)
|
|
79
|
-
),
|
|
80
|
-
ejected: structuredClone(defaults.files.ejected || [])
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
63
|
function detectProjectName(): string {
|
|
85
64
|
try {
|
|
86
65
|
const url = execSync('git remote get-url origin', { stdio: ['pipe', 'pipe', 'pipe'] })
|
|
@@ -222,11 +201,12 @@ async function cmdInit(): Promise<void> {
|
|
|
222
201
|
);
|
|
223
202
|
}
|
|
224
203
|
|
|
225
|
-
|
|
204
|
+
const adapters = listAgentClientAdapters();
|
|
205
|
+
let enabledClientIds: string[];
|
|
226
206
|
try {
|
|
227
|
-
|
|
228
|
-
'
|
|
229
|
-
|
|
207
|
+
enabledClientIds = await multiSelect(
|
|
208
|
+
'Agent Client project integrations to enable',
|
|
209
|
+
adapters.map((adapter) => ({ id: adapter.id, label: adapter.displayName }))
|
|
230
210
|
);
|
|
231
211
|
} catch (e) {
|
|
232
212
|
err(e instanceof Error ? e.message : String(e));
|
|
@@ -234,7 +214,7 @@ async function cmdInit(): Promise<void> {
|
|
|
234
214
|
process.exitCode = 1;
|
|
235
215
|
return;
|
|
236
216
|
}
|
|
237
|
-
const
|
|
217
|
+
const enabledClientSet = new Set(enabledClientIds);
|
|
238
218
|
|
|
239
219
|
const templateSources = parseLocalSources(await prompt(
|
|
240
220
|
'Template sources (optional, comma-separated local paths, e.g. ~/my-templates; Enter to skip)',
|
|
@@ -249,6 +229,39 @@ async function cmdInit(): Promise<void> {
|
|
|
249
229
|
const project = projectName;
|
|
250
230
|
const replacements = { project, org: orgName };
|
|
251
231
|
|
|
232
|
+
const defaultState = Object.fromEntries(defaults.agentClients.map((entry) => [
|
|
233
|
+
entry.id,
|
|
234
|
+
{
|
|
235
|
+
enabled: enabledClientSet.has(entry.id),
|
|
236
|
+
installInSandbox: entry.installInSandbox
|
|
237
|
+
}
|
|
238
|
+
])) as AgentClientState;
|
|
239
|
+
const config: AgentConfig = {
|
|
240
|
+
project: projectName,
|
|
241
|
+
org: orgName,
|
|
242
|
+
language,
|
|
243
|
+
platform: { type: platformType },
|
|
244
|
+
templateVersion: VERSION,
|
|
245
|
+
sandbox: structuredClone(defaults.sandbox),
|
|
246
|
+
task: structuredClone(defaults.task),
|
|
247
|
+
labels: structuredClone(defaults.labels),
|
|
248
|
+
files: { managed: [], merged: [], ejected: [] },
|
|
249
|
+
agentClients: serializeAgentClients(defaultState)
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
if (sandboxEngine) config.sandbox.engine = sandboxEngine;
|
|
253
|
+
if (templateSources.length > 0) config.templates = { sources: templateSources };
|
|
254
|
+
if (skillSources.length > 0) config.skills = { sources: skillSources };
|
|
255
|
+
|
|
256
|
+
const workflowPlan = planAgentClientReconciliation({
|
|
257
|
+
config: config as unknown as Record<string, unknown>,
|
|
258
|
+
mutation: { type: 'none' },
|
|
259
|
+
projectRoot: process.cwd(),
|
|
260
|
+
templateRoot: templateDir,
|
|
261
|
+
platformType,
|
|
262
|
+
language
|
|
263
|
+
});
|
|
264
|
+
|
|
252
265
|
console.log('');
|
|
253
266
|
if (orgName) {
|
|
254
267
|
info(`Installing update-agent-infra seed command for: ${projectName} (${orgName})`);
|
|
@@ -257,18 +270,6 @@ async function cmdInit(): Promise<void> {
|
|
|
257
270
|
}
|
|
258
271
|
console.log('');
|
|
259
272
|
|
|
260
|
-
// select language-specific template filenames
|
|
261
|
-
let claudeSrc, geminiSrc, opencodeSrc;
|
|
262
|
-
if (language === 'zh-CN') {
|
|
263
|
-
claudeSrc = 'update-agent-infra.zh-CN.md';
|
|
264
|
-
geminiSrc = 'update-agent-infra.zh-CN.toml';
|
|
265
|
-
opencodeSrc = 'update-agent-infra.zh-CN.md';
|
|
266
|
-
} else {
|
|
267
|
-
claudeSrc = 'update-agent-infra.en.md';
|
|
268
|
-
geminiSrc = 'update-agent-infra.en.toml';
|
|
269
|
-
opencodeSrc = 'update-agent-infra.en.md';
|
|
270
|
-
}
|
|
271
|
-
|
|
272
273
|
// install skill
|
|
273
274
|
copySkillDir(
|
|
274
275
|
path.join(templateDir, '.agents', 'skills', 'update-agent-infra'),
|
|
@@ -285,68 +286,8 @@ async function cmdInit(): Promise<void> {
|
|
|
285
286
|
);
|
|
286
287
|
ok('Installed .agents/scripts/lib/agent-infra-package.js');
|
|
287
288
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
renderFile(
|
|
291
|
-
path.join(templateDir, '.claude', 'commands', claudeSrc),
|
|
292
|
-
path.join('.claude', 'commands', 'update-agent-infra.md'),
|
|
293
|
-
replacements
|
|
294
|
-
);
|
|
295
|
-
ok('Installed .claude/commands/update-agent-infra.md');
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// install Gemini command (only if enabled)
|
|
299
|
-
if (enabledTUISet.has('gemini-cli')) {
|
|
300
|
-
renderFile(
|
|
301
|
-
path.join(templateDir, '.gemini', 'commands', '_project_', geminiSrc),
|
|
302
|
-
path.join('.gemini', 'commands', project, 'update-agent-infra.toml'),
|
|
303
|
-
replacements
|
|
304
|
-
);
|
|
305
|
-
ok(`Installed .gemini/commands/${project}/update-agent-infra.toml`);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// install OpenCode command (only if enabled)
|
|
309
|
-
if (enabledTUISet.has('opencode')) {
|
|
310
|
-
renderFile(
|
|
311
|
-
path.join(templateDir, '.opencode', 'commands', opencodeSrc),
|
|
312
|
-
path.join('.opencode', 'commands', 'update-agent-infra.md'),
|
|
313
|
-
replacements
|
|
314
|
-
);
|
|
315
|
-
ok('Installed .opencode/commands/update-agent-infra.md');
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// generate .agents/.airc.json
|
|
319
|
-
const config: AgentConfig = {
|
|
320
|
-
project: projectName,
|
|
321
|
-
org: orgName,
|
|
322
|
-
language,
|
|
323
|
-
platform: { type: platformType },
|
|
324
|
-
templateVersion: VERSION,
|
|
325
|
-
sandbox: structuredClone(defaults.sandbox),
|
|
326
|
-
task: structuredClone(defaults.task),
|
|
327
|
-
labels: structuredClone(defaults.labels),
|
|
328
|
-
files: buildDefaultFiles(platformType, enabledTUISet),
|
|
329
|
-
tuis: enabledTUIs
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
if (sandboxEngine) {
|
|
333
|
-
config.sandbox.engine = sandboxEngine;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (templateSources.length > 0) {
|
|
337
|
-
config.templates = {
|
|
338
|
-
sources: templateSources
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if (skillSources.length > 0) {
|
|
343
|
-
config.skills = {
|
|
344
|
-
sources: skillSources
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
349
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
289
|
+
const reconcileResult = applyAgentClientReconciliation(workflowPlan);
|
|
290
|
+
for (const target of reconcileResult.applied) ok(`Installed ${target}`);
|
|
350
291
|
ok(`Generated ${configPath}`);
|
|
351
292
|
|
|
352
293
|
// done
|
|
@@ -356,24 +297,15 @@ async function cmdInit(): Promise<void> {
|
|
|
356
297
|
console.log(' If this init used npx, install agent-infra persistently before update or validation:');
|
|
357
298
|
console.log(' npm install -g @fitlab-ai/agent-infra');
|
|
358
299
|
console.log('');
|
|
359
|
-
if (
|
|
360
|
-
console.log(' No
|
|
300
|
+
if (workflowPlan.nextSteps.length === 0) {
|
|
301
|
+
console.log(' No Agent Client project integration enabled.');
|
|
361
302
|
console.log(` Configure "customTUIs" in ${configPath} before running update-agent-infra.`);
|
|
362
303
|
console.log('');
|
|
363
304
|
} else {
|
|
364
305
|
console.log(' Next step: open this project in any AI TUI and run:');
|
|
365
306
|
console.log('');
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
if (enabledTUISet.has('opencode')) claudeOrOpencode.push('OpenCode');
|
|
369
|
-
if (claudeOrOpencode.length > 0) {
|
|
370
|
-
console.log(` ${claudeOrOpencode.join(' / ')}: /update-agent-infra`);
|
|
371
|
-
}
|
|
372
|
-
if (enabledTUISet.has('gemini-cli')) {
|
|
373
|
-
console.log(` Gemini CLI: /${project}:update-agent-infra`);
|
|
374
|
-
}
|
|
375
|
-
if (enabledTUISet.has('codex')) {
|
|
376
|
-
console.log(' Codex CLI: $update-agent-infra');
|
|
307
|
+
for (const nextStep of workflowPlan.nextSteps) {
|
|
308
|
+
console.log(` ${nextStep.displayName}: ${nextStep.command}`);
|
|
377
309
|
}
|
|
378
310
|
console.log('');
|
|
379
311
|
console.log(' This will render all templates and set up the full');
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
AgentClientConfigError,
|
|
6
|
+
normalizeAgentClients
|
|
7
|
+
} from '../agent-clients/config.ts';
|
|
8
|
+
import { normalizeCustomTUIs } from '../agent-clients/custom-tuis.ts';
|
|
9
|
+
import { renderNextStepCommands } from '../agent-clients/next-steps.ts';
|
|
10
|
+
|
|
11
|
+
const USAGE = 'Usage: agent-infra-internal agent-client next-steps --skill <skill-name> [--task-ref <NN|TASK-id>] [--format text|json]\n';
|
|
12
|
+
|
|
13
|
+
type ParsedArgs = Readonly<{
|
|
14
|
+
skillName: string;
|
|
15
|
+
taskRef?: string;
|
|
16
|
+
format: 'text' | 'json';
|
|
17
|
+
}>;
|
|
18
|
+
|
|
19
|
+
function failure(code: string, message: string): void {
|
|
20
|
+
process.stdout.write(`${JSON.stringify({
|
|
21
|
+
status: 'failed',
|
|
22
|
+
changed: false,
|
|
23
|
+
commands: [],
|
|
24
|
+
diagnostics: [],
|
|
25
|
+
error: { code, message }
|
|
26
|
+
})}\n`);
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseArgs(args: string[]): ParsedArgs | null {
|
|
31
|
+
if (args[0] === '--help' || args[0] === '-h') {
|
|
32
|
+
process.stdout.write(USAGE);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (args[0] !== 'next-steps') {
|
|
36
|
+
failure('AGENT_CLIENT_PAYLOAD_INVALID', "operation must be 'next-steps'");
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
let skillName: string | undefined;
|
|
40
|
+
let taskRef: string | undefined;
|
|
41
|
+
let format: 'text' | 'json' = 'text';
|
|
42
|
+
const seen = new Set<string>();
|
|
43
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
44
|
+
const flag = args[index]!;
|
|
45
|
+
if (!['--skill', '--task-ref', '--format'].includes(flag)) {
|
|
46
|
+
failure('AGENT_CLIENT_PAYLOAD_INVALID', `unknown option '${flag}'`);
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (seen.has(flag)) {
|
|
50
|
+
failure('AGENT_CLIENT_PAYLOAD_INVALID', `duplicate option '${flag}'`);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const value = args[++index];
|
|
54
|
+
if (!value || value.startsWith('--')) {
|
|
55
|
+
failure('AGENT_CLIENT_PAYLOAD_INVALID', `option '${flag}' requires a value`);
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
seen.add(flag);
|
|
59
|
+
if (flag === '--skill') skillName = value;
|
|
60
|
+
if (flag === '--task-ref') taskRef = value;
|
|
61
|
+
if (flag === '--format') {
|
|
62
|
+
if (value !== 'text' && value !== 'json') {
|
|
63
|
+
failure('AGENT_CLIENT_PAYLOAD_INVALID', "format must be 'text' or 'json'");
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
format = value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (!skillName) {
|
|
70
|
+
failure('AGENT_CLIENT_PAYLOAD_INVALID', "option '--skill' is required");
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return { skillName, ...(taskRef ? { taskRef } : {}), format };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function agentClient(args: string[] = []): void {
|
|
77
|
+
const parsed = parseArgs(args);
|
|
78
|
+
if (!parsed || process.exitCode) return;
|
|
79
|
+
|
|
80
|
+
let config: Record<string, unknown>;
|
|
81
|
+
try {
|
|
82
|
+
const configPath = path.join(process.cwd(), '.agents', '.airc.json');
|
|
83
|
+
const raw = JSON.parse(fs.readFileSync(configPath, 'utf8')) as unknown;
|
|
84
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
85
|
+
throw new Error('configuration root must be an object');
|
|
86
|
+
}
|
|
87
|
+
config = raw as Record<string, unknown>;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
failure('AGENT_CLIENT_CONFIG_INVALID', String(error));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const clients = normalizeAgentClients(config);
|
|
95
|
+
const custom = normalizeCustomTUIs(process.cwd(), config.customTUIs ?? []);
|
|
96
|
+
const commands = renderNextStepCommands({
|
|
97
|
+
projectName: String(config.project ?? ''),
|
|
98
|
+
state: clients.state,
|
|
99
|
+
customTUIs: custom.items,
|
|
100
|
+
skillName: parsed.skillName,
|
|
101
|
+
...(parsed.taskRef ? { taskRef: parsed.taskRef } : {})
|
|
102
|
+
});
|
|
103
|
+
if (parsed.format === 'json') {
|
|
104
|
+
process.stdout.write(`${JSON.stringify({
|
|
105
|
+
status: 'rendered',
|
|
106
|
+
changed: false,
|
|
107
|
+
commands,
|
|
108
|
+
diagnostics: custom.diagnostics,
|
|
109
|
+
error: null
|
|
110
|
+
})}\n`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
for (const command of commands) {
|
|
114
|
+
process.stdout.write(` - ${command.displayName}: ${command.command}\n`);
|
|
115
|
+
}
|
|
116
|
+
for (const diagnostic of custom.diagnostics) {
|
|
117
|
+
process.stderr.write(`${diagnostic.code} at ${diagnostic.path}\n`);
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (error instanceof AgentClientConfigError) {
|
|
121
|
+
failure(error.code, error.message);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
failure(
|
|
125
|
+
'AGENT_CLIENT_RENDER_INVALID',
|
|
126
|
+
error instanceof Error ? error.message : String(error)
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export { agentClient };
|
|
@@ -27,7 +27,7 @@ function gitWorkflow(args: string[] = []): void {
|
|
|
27
27
|
const inputIndex = rest.indexOf('--input');
|
|
28
28
|
const input = inputIndex >= 0 && rest[inputIndex + 1] ? JSON.parse(fs.readFileSync(rest[inputIndex + 1]!, 'utf8')) : null;
|
|
29
29
|
if (!input) result = { status: 'failed', changed: false, error: { code: 'GIT_INPUT_REQUIRED', message: '--input JSON file is required' } };
|
|
30
|
-
else if (action === 'snapshot') result = { status: 'no-op', changed: false, snapshot: snapshotReview({ cwd: cwd ?? process.cwd(), mode: input.mode, baseline: input.baseline, globs: input.globs ?? resolvePostReviewGlobs({}, {}) }), error: null };
|
|
30
|
+
else if (action === 'snapshot') result = { status: 'no-op', changed: false, snapshot: snapshotReview({ cwd: cwd ?? process.cwd(), mode: input.mode, baseline: input.baseline, diffBase: input.diffBase, globs: input.globs ?? resolvePostReviewGlobs({}, {}) }), error: null };
|
|
31
31
|
else result = { status: 'no-op', changed: false, comparison: compareReviewTrees({ cwd: cwd ?? process.cwd(), expected: input.expected, actual: input.actual }), error: null };
|
|
32
32
|
} else result = { status: 'failed', changed: false, error: { code: 'GIT_ACTION_INVALID', message: `Unknown git-workflow action '${action ?? ''}'` } };
|
|
33
33
|
output(result);
|
|
@@ -150,11 +150,58 @@ function runOptionalDemo(cwd: string, run: CommandRunner = command): DemoResult
|
|
|
150
150
|
return { status: 'recorded', reasonCode: null, message: null, outputPath };
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
function git(cwd: string, args: string[]): string | null {
|
|
154
|
-
const result =
|
|
153
|
+
function git(cwd: string, args: string[], run: CommandRunner = command): string | null {
|
|
154
|
+
const result = run(cwd, 'git', args);
|
|
155
155
|
return result.status === 0 ? String(result.stdout).trim() : null;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
function inspectLocalReleaseFacts(cwd: string, version: string, run: CommandRunner = command) {
|
|
159
|
+
const tag = `v${version}`;
|
|
160
|
+
const head = git(cwd, ['rev-parse', 'HEAD'], run);
|
|
161
|
+
const tagCommit = git(cwd, ['rev-parse', '--verify', `${tag}^{commit}`], run);
|
|
162
|
+
const localTag = Boolean(tagCommit && tagCommit === head);
|
|
163
|
+
const localTagAncestor = Boolean(
|
|
164
|
+
tagCommit && head && !localTag
|
|
165
|
+
&& run(cwd, 'git', ['merge-base', '--is-ancestor', tagCommit, head]).status === 0
|
|
166
|
+
);
|
|
167
|
+
const localTagConflict = Boolean(tagCommit && !localTag && !localTagAncestor);
|
|
168
|
+
const postSubject = `chore: prepare next dev iteration after v${version}`;
|
|
169
|
+
const postLog = localTag || localTagAncestor
|
|
170
|
+
? git(cwd, ['log', '--format=%s', `${tag}..HEAD`], run) ?? ''
|
|
171
|
+
: '';
|
|
172
|
+
const postCommit = postLog.split('\n').some((subject) => subject === postSubject);
|
|
173
|
+
return { localTag, localTagAncestor, localTagConflict, postCommit };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function releaseSmokeStatus(workflows: Array<Record<string, unknown>>, version: string, tagCommit: string | null): ReleaseFacts['smoke'] {
|
|
177
|
+
const manualTitle = `Post-Release Smoke v${version}`;
|
|
178
|
+
const targets = workflows.filter((run) => {
|
|
179
|
+
if (String(run.workflowName || run.name) !== 'Post-Release Smoke') return false;
|
|
180
|
+
if (String(run.event) === 'workflow_run') return Boolean(tagCommit && String(run.headSha) === tagCommit);
|
|
181
|
+
return String(run.event) === 'workflow_dispatch' && String(run.displayTitle) === manualTitle;
|
|
182
|
+
}).sort((left, right) => {
|
|
183
|
+
const leftOrder = [Date.parse(String(left.createdAt || '')) || 0, Number(left.databaseId) || 0, Number(left.attempt) || 0];
|
|
184
|
+
const rightOrder = [Date.parse(String(right.createdAt || '')) || 0, Number(right.databaseId) || 0, Number(right.attempt) || 0];
|
|
185
|
+
for (let index = 0; index < leftOrder.length; index += 1) {
|
|
186
|
+
if (leftOrder[index] !== rightOrder[index]) return rightOrder[index]! - leftOrder[index]!;
|
|
187
|
+
}
|
|
188
|
+
return 0;
|
|
189
|
+
});
|
|
190
|
+
const latest = targets[0];
|
|
191
|
+
if (!latest) return null;
|
|
192
|
+
if (String(latest.status) !== 'completed') return 'pending';
|
|
193
|
+
return String(latest.conclusion) === 'success' ? 'success' : 'failed';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function inspectPostWorktree(cwd: string) {
|
|
197
|
+
const inspected = inspectGitWorkflow(cwd);
|
|
198
|
+
if (!inspected.snapshot) return inspected.error ?? { code: 'GIT_INSPECT_FAILED', message: 'Unable to inspect Git repository' };
|
|
199
|
+
if (inspected.snapshot.worktree.length || inspected.snapshot.staged.length) {
|
|
200
|
+
return { code: 'WORKTREE_DIRTY', message: 'Release post requires a clean worktree' };
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
158
205
|
async function inspectFacts(cwd: string, version: string): Promise<ReleaseFacts> {
|
|
159
206
|
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8')) as { name: string };
|
|
160
207
|
const config = JSON.parse(fs.readFileSync(path.join(cwd, '.agents', '.airc.json'), 'utf8')) as { project: string; org: string };
|
|
@@ -162,6 +209,7 @@ async function inspectFacts(cwd: string, version: string): Promise<ReleaseFacts>
|
|
|
162
209
|
const branch = git(cwd, ['branch', '--show-current']) || '';
|
|
163
210
|
const head = git(cwd, ['rev-parse', 'HEAD']);
|
|
164
211
|
const localTagTarget = git(cwd, ['rev-parse', '--verify', `${tag}^{commit}`]);
|
|
212
|
+
const local = inspectLocalReleaseFacts(cwd, version);
|
|
165
213
|
const remoteBranch = branch ? git(cwd, ['ls-remote', '--heads', 'origin', branch]) : null;
|
|
166
214
|
const remoteTag = git(cwd, ['ls-remote', '--tags', 'origin', `refs/tags/${tag}`]);
|
|
167
215
|
const platform = inspectPlatformRelease(tag, { cwd });
|
|
@@ -169,18 +217,14 @@ async function inspectFacts(cwd: string, version: string): Promise<ReleaseFacts>
|
|
|
169
217
|
const formulaUrl = `https://raw.githubusercontent.com/${config.org}/homebrew-tap/main/Formula/${config.project}.rb`;
|
|
170
218
|
const homebrew = await inspectHomebrewChannel(formulaUrl, version);
|
|
171
219
|
const workflows = platform.workflows as Array<Record<string, unknown>>;
|
|
172
|
-
const
|
|
173
|
-
const smoke = smokeRun ? String(smokeRun.conclusion) === 'success' ? 'success' : String(smokeRun.status) === 'completed' ? 'failed' : 'pending' : null;
|
|
174
|
-
const postMessage = git(cwd, ['log', '-1', '--pretty=%s']) || '';
|
|
220
|
+
const smoke = releaseSmokeStatus(workflows, version, localTagTarget);
|
|
175
221
|
return {
|
|
176
|
-
|
|
177
|
-
localTagConflict: Boolean(localTagTarget && localTagTarget !== head),
|
|
222
|
+
...local,
|
|
178
223
|
remoteBranch: Boolean(remoteBranch && remoteBranch.split(/\s+/)[0] === head), remoteTag: Boolean(remoteTag),
|
|
179
224
|
githubRelease: platform.platform.type !== 'github'
|
|
180
225
|
? true
|
|
181
226
|
: platform.status === 'blocked' ? null : Boolean(platform.release?.published),
|
|
182
|
-
npm: npm.published, homebrew: homebrew.published, smoke
|
|
183
|
-
postCommit: postMessage.includes(`after v${version}`)
|
|
227
|
+
npm: npm.published, homebrew: homebrew.published, smoke
|
|
184
228
|
};
|
|
185
229
|
}
|
|
186
230
|
|
|
@@ -218,12 +262,11 @@ async function releaseWorkflow(args: string[] = []): Promise<void> {
|
|
|
218
262
|
}
|
|
219
263
|
const before = releaseSnapshot(version, await inspectFacts(cwd, version));
|
|
220
264
|
if (action === 'inspect') { process.stdout.write(`${JSON.stringify({ status: 'no-op', changed: false, snapshot: before, error: null })}\n`); return; }
|
|
265
|
+
if (before.facts.localTagConflict) {
|
|
266
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: { code: 'GIT_TAG_CONFLICT', message: `Tag v${version} is not reachable from HEAD` } })}\n`);
|
|
267
|
+
process.exitCode = 1; return;
|
|
268
|
+
}
|
|
221
269
|
if (action === 'prepare') {
|
|
222
|
-
if (before.facts.localTagConflict) {
|
|
223
|
-
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: { code: 'GIT_TAG_CONFLICT', message: `Tag v${version} does not point to HEAD` } })}\n`);
|
|
224
|
-
process.exitCode = 1;
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
270
|
if (before.phase !== 'unprepared') {
|
|
228
271
|
const milestones = reconcileReleaseMilestones(version, { cwd });
|
|
229
272
|
process.stdout.write(`${JSON.stringify({ ...milestones, snapshot: before })}\n`);
|
|
@@ -254,16 +297,25 @@ async function releaseWorkflow(args: string[] = []): Promise<void> {
|
|
|
254
297
|
process.exitCode = failed ? 1 : blocked ? 2 : 0; return;
|
|
255
298
|
}
|
|
256
299
|
if (action === 'publish') {
|
|
257
|
-
if (!['prepared', 'partially-published'].includes(before.phase)) { process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: { code: 'RELEASE_PHASE_INVALID', message: `Cannot publish from ${before.phase}` } })}\n`); process.exitCode = 1; return; }
|
|
300
|
+
if (!before.facts.localTag || !['prepared', 'partially-published'].includes(before.phase)) { process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: { code: 'RELEASE_PHASE_INVALID', message: `Cannot publish from ${before.phase}` } })}\n`); process.exitCode = 1; return; }
|
|
258
301
|
const branch = git(cwd, ['branch', '--show-current']) || '';
|
|
259
302
|
const refs = [...(!before.facts.remoteBranch ? [branch] : []), ...(!before.facts.remoteTag ? [`refs/tags/v${version}`] : [])];
|
|
260
303
|
const result = pushGitRefs({ cwd, remote: 'origin', refs });
|
|
261
304
|
process.stdout.write(`${JSON.stringify({ ...result, snapshot: releaseSnapshot(version, await inspectFacts(cwd, version)) })}\n`); process.exitCode = result.status === 'failed' ? 1 : result.status === 'degraded' ? 2 : 0; return;
|
|
262
305
|
}
|
|
306
|
+
if (before.phase === 'complete') {
|
|
307
|
+
process.stdout.write(`${JSON.stringify({ status: 'no-op', changed: false, snapshot: before, error: null })}\n`);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
263
310
|
const channelsComplete = before.facts.githubRelease === true && before.facts.npm === true && before.facts.homebrew === true;
|
|
264
311
|
if (!['published', 'post-pending'].includes(before.phase) || !channelsComplete || before.facts.smoke !== 'success') {
|
|
265
312
|
process.stdout.write(`${JSON.stringify({ status: before.facts.smoke === 'failed' ? 'failed' : 'blocked', changed: false, snapshot: before, error: { code: 'RELEASE_CHANNELS_PENDING', message: 'Release channels or smoke workflow are not complete' } })}\n`); process.exitCode = before.facts.smoke === 'failed' ? 1 : 2; return;
|
|
266
313
|
}
|
|
314
|
+
const worktreeError = inspectPostWorktree(cwd);
|
|
315
|
+
if (worktreeError) {
|
|
316
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: worktreeError })}\n`);
|
|
317
|
+
process.exitCode = 1; return;
|
|
318
|
+
}
|
|
267
319
|
const built = command(cwd, 'npm', ['run', 'build']);
|
|
268
320
|
if (built.status !== 0) {
|
|
269
321
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: true, snapshot: before, error: { code: 'RELEASE_POST_COMMAND_FAILED', message: String(built.stderr || built.stdout) } })}\n`);
|
|
@@ -284,5 +336,5 @@ async function releaseWorkflow(args: string[] = []): Promise<void> {
|
|
|
284
336
|
process.stdout.write(`${JSON.stringify({ ...pushed, demo, snapshot: releaseSnapshot(version, await inspectFacts(cwd, version)) })}\n`); process.exitCode = pushed.status === 'failed' ? 1 : pushed.status === 'degraded' ? 2 : 0;
|
|
285
337
|
}
|
|
286
338
|
|
|
287
|
-
export { computeDemoInputDigest, inspectFacts, releaseWorkflow, runOptionalDemo };
|
|
339
|
+
export { computeDemoInputDigest, inspectFacts, inspectLocalReleaseFacts, inspectPostWorktree, releaseSmokeStatus, releaseWorkflow, runOptionalDemo };
|
|
288
340
|
export type { CommandRunner, DemoResult };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import {
|
|
2
|
+
activateMatchingOrchestrationDelegation,
|
|
3
|
+
activateOrchestrationDelegation,
|
|
4
|
+
advanceOrchestration,
|
|
5
|
+
beginOrResumeOrchestration,
|
|
6
|
+
completeCommitOrchestrationStage,
|
|
7
|
+
pauseOrchestration,
|
|
8
|
+
prepareOrchestrationDelegation,
|
|
9
|
+
routeOrchestration,
|
|
10
|
+
sealMatchingOrchestrationDelegation,
|
|
11
|
+
sealOrchestrationDelegation,
|
|
12
|
+
statusOrchestration
|
|
13
|
+
} from '../task/orchestration.ts';
|
|
14
|
+
import type { AgentClientId } from '../agent-clients/types.ts';
|
|
15
|
+
|
|
16
|
+
const USAGE = 'Usage: agent-infra-internal task-orchestration <task-ref|auto> <begin-or-resume|route|prepare|hook-start|stage-completed|hook-stop|advance|pause|status> [options]\n';
|
|
17
|
+
|
|
18
|
+
function usageFailure(message: string): void {
|
|
19
|
+
process.stdout.write(`${JSON.stringify({
|
|
20
|
+
status: 'failed', changed: false,
|
|
21
|
+
error: { code: 'ORCHESTRATION_PAYLOAD_INVALID', message }
|
|
22
|
+
})}\n`);
|
|
23
|
+
process.stderr.write(USAGE);
|
|
24
|
+
process.exitCode = 2;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function taskOrchestration(args: string[] = []): void {
|
|
28
|
+
if (args[0] === '--help' || args[0] === '-h') {
|
|
29
|
+
process.stdout.write(USAGE);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (args.length < 2) {
|
|
33
|
+
usageFailure('task ref and intent are required');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const [taskRef, intent] = args;
|
|
37
|
+
if (!['begin-or-resume', 'route', 'prepare', 'hook-start', 'stage-completed', 'hook-stop', 'advance', 'pause', 'status'].includes(intent!)) {
|
|
38
|
+
usageFailure(`unknown intent '${intent}'`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const values: Record<string, string> = {};
|
|
42
|
+
const seen = new Set<string>();
|
|
43
|
+
for (let index = 2; index < args.length; index += 1) {
|
|
44
|
+
const flag = args[index]!;
|
|
45
|
+
if (![
|
|
46
|
+
'--max-steps', '--client', '--requested-model', '--parent-id', '--before-fingerprint',
|
|
47
|
+
'--native-agent', '--child-id', '--spawn-mode', '--actual-model', '--fallback-reason',
|
|
48
|
+
'--exit-code', '--after-fingerprint', '--changed-paths', '--code', '--message', '--recoverable', '--agent'
|
|
49
|
+
].includes(flag)) {
|
|
50
|
+
usageFailure(`unknown option '${flag}'`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (seen.has(flag)) {
|
|
54
|
+
usageFailure(`duplicate option '${flag}'`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
seen.add(flag);
|
|
58
|
+
const value = args[++index];
|
|
59
|
+
if (!value || value.startsWith('--')) {
|
|
60
|
+
usageFailure(`option '${flag}' requires a value`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
values[flag] = value;
|
|
64
|
+
}
|
|
65
|
+
const requireValues = (flags: string[]) => flags.find((flag) => values[flag] === undefined);
|
|
66
|
+
let result;
|
|
67
|
+
if (intent === 'begin-or-resume') {
|
|
68
|
+
const maxSteps = values['--max-steps'] === undefined ? undefined : Number(values['--max-steps']);
|
|
69
|
+
if (maxSteps !== undefined && (!Number.isInteger(maxSteps) || maxSteps < 1)) {
|
|
70
|
+
usageFailure('--max-steps must be a positive integer'); return;
|
|
71
|
+
}
|
|
72
|
+
result = beginOrResumeOrchestration(taskRef!, { maxSteps });
|
|
73
|
+
} else if (intent === 'route') {
|
|
74
|
+
result = routeOrchestration(taskRef!);
|
|
75
|
+
} else if (intent === 'status') {
|
|
76
|
+
result = statusOrchestration(taskRef!);
|
|
77
|
+
} else if (intent === 'prepare') {
|
|
78
|
+
const missing = requireValues(['--client']);
|
|
79
|
+
if (missing) { usageFailure(`intent 'prepare' requires '${missing}'`); return; }
|
|
80
|
+
result = prepareOrchestrationDelegation(taskRef!, {
|
|
81
|
+
client: values['--client'] as AgentClientId,
|
|
82
|
+
requestedModel: values['--requested-model']
|
|
83
|
+
});
|
|
84
|
+
} else if (intent === 'hook-start') {
|
|
85
|
+
const missing = requireValues([
|
|
86
|
+
...(taskRef === 'auto' ? ['--client'] : []),
|
|
87
|
+
'--native-agent', '--child-id', '--parent-id', '--spawn-mode'
|
|
88
|
+
]);
|
|
89
|
+
if (missing) { usageFailure(`intent 'hook-start' requires '${missing}'`); return; }
|
|
90
|
+
const event = {
|
|
91
|
+
nativeAgent: values['--native-agent']!, childId: values['--child-id']!,
|
|
92
|
+
parentId: values['--parent-id']!, spawnMode: values['--spawn-mode']!,
|
|
93
|
+
actualModel: values['--actual-model'], modelFallbackReason: values['--fallback-reason']
|
|
94
|
+
};
|
|
95
|
+
result = taskRef === 'auto'
|
|
96
|
+
? activateMatchingOrchestrationDelegation(values['--client'] as AgentClientId, event)
|
|
97
|
+
: activateOrchestrationDelegation(taskRef!, event);
|
|
98
|
+
} else if (intent === 'hook-stop') {
|
|
99
|
+
if (taskRef === 'auto') {
|
|
100
|
+
const missing = requireValues(['--client', '--native-agent', '--child-id']);
|
|
101
|
+
if (missing) { usageFailure(`intent 'hook-stop' requires '${missing}'`); return; }
|
|
102
|
+
result = sealMatchingOrchestrationDelegation(values['--client'] as AgentClientId, {
|
|
103
|
+
nativeAgent: values['--native-agent']!, childId: values['--child-id']!
|
|
104
|
+
});
|
|
105
|
+
} else {
|
|
106
|
+
const missing = requireValues(['--child-id', '--exit-code', '--after-fingerprint']);
|
|
107
|
+
if (missing) { usageFailure(`intent 'hook-stop' requires '${missing}'`); return; }
|
|
108
|
+
const exitCode = Number(values['--exit-code']);
|
|
109
|
+
if (!Number.isInteger(exitCode)) { usageFailure('--exit-code must be an integer'); return; }
|
|
110
|
+
result = sealOrchestrationDelegation(taskRef!, {
|
|
111
|
+
childId: values['--child-id']!, exitCode,
|
|
112
|
+
afterFingerprint: values['--after-fingerprint']!,
|
|
113
|
+
changedPaths: values['--changed-paths'] ? values['--changed-paths']!.split(',').filter(Boolean) : []
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
} else if (intent === 'stage-completed') {
|
|
117
|
+
const missing = requireValues(['--agent']);
|
|
118
|
+
if (missing) { usageFailure(`intent 'stage-completed' requires '${missing}'`); return; }
|
|
119
|
+
result = completeCommitOrchestrationStage(taskRef!, values['--agent']!);
|
|
120
|
+
} else if (intent === 'advance') {
|
|
121
|
+
result = advanceOrchestration(taskRef!);
|
|
122
|
+
} else {
|
|
123
|
+
const missing = requireValues(['--code', '--message', '--recoverable']);
|
|
124
|
+
if (missing) { usageFailure(`intent 'pause' requires '${missing}'`); return; }
|
|
125
|
+
if (!['true', 'false'].includes(values['--recoverable']!)) { usageFailure('--recoverable must be true or false'); return; }
|
|
126
|
+
result = pauseOrchestration(taskRef!, values['--code']!, values['--message']!, values['--recoverable'] === 'true');
|
|
127
|
+
}
|
|
128
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
129
|
+
if (result.status === 'failed') process.exitCode = 1;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export { taskOrchestration };
|