@moretea-labs/forge 1.5.0-rc.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -11
- package/CHANGELOG.md +19 -0
- package/CLAUDE.md +8 -10
- package/DESIGN.md +278 -0
- package/README.md +70 -90
- package/README.zh-CN.md +50 -55
- package/SKILL.md +8 -11
- package/THIRD_PARTY_NOTICES.md +9 -3
- package/assets/branding/README.md +4 -2
- package/assets/hooks/lib/workflow-state.sh +19 -18
- package/assets/hooks/post-edit-guard.sh +6 -75
- package/assets/hooks/prompt-guard.sh +24 -12
- package/assets/hooks/session-start-context.sh +17 -17
- package/assets/hooks/stop-orchestrator.sh +2 -2
- package/assets/partials/04-project-structure.partial.md +6 -6
- package/assets/partials/05-workflow.partial.md +6 -6
- package/assets/partials/07-footer.partial.md +1 -1
- package/assets/partials/08-orchestration.partial.md +2 -2
- package/assets/partials-agents/02-operating-mode.partial.md +5 -5
- package/assets/partials-agents/03-orchestration.partial.md +3 -3
- package/assets/partials-agents/04-task-protocol.partial.md +3 -3
- package/assets/partials-agents/08-deep-docs.partial.md +1 -1
- package/assets/reference-configs/agentic-development-flow.md +49 -86
- package/assets/reference-configs/external-tooling.md +21 -61
- package/assets/reference-configs/handoff-protocol.md +25 -20
- package/assets/reference-configs/harness-overview.md +8 -8
- package/assets/templates/helpers/archive-workflow.sh +0 -1
- package/assets/templates/helpers/capture-plan.sh +3 -3
- package/assets/templates/helpers/check-task-workflow.sh +2 -2
- package/assets/templates/helpers/codex-handoff-resume.sh +5 -5
- package/assets/templates/helpers/ensure-task-workflow.sh +37 -35
- package/assets/templates/helpers/new-plan.sh +1 -1
- package/assets/templates/helpers/plan-to-todo.sh +2 -2
- package/assets/templates/helpers/prepare-codex-handoff.sh +2 -2
- package/assets/templates/helpers/prepare-handoff.sh +9 -9
- package/assets/templates/helpers/refresh-current-status.sh +2 -2
- package/assets/templates/helpers/ship-worktrees.sh +1 -1
- package/assets/templates/helpers/sprint-backlog.sh +1 -1
- package/assets/templates/helpers/verify-sprint.sh +7 -7
- package/assets/templates/helpers/workstream-sync.sh +1 -1
- package/assets/templates/plan.template.md +1 -1
- package/assets/workflow-contract.v1.json +6 -3
- package/docs/architecture/current/README.md +10 -9
- package/docs/architecture/current/approved-target-architecture.zh-CN.md +2 -2
- package/docs/architecture/current/architecture-invariants.md +2 -2
- package/docs/architecture/current/automation-and-schedule-engine.md +15 -15
- package/docs/architecture/current/codegraph-context-provider.md +2 -2
- package/docs/architecture/current/control-plane-authority-inventory.md +12 -0
- package/docs/architecture/current/dispatch-and-agent-strategy.md +11 -11
- package/docs/architecture/current/entity-model.md +0 -27
- package/docs/architecture/current/failure-recovery.md +0 -24
- package/docs/architecture/current/governance.md +1 -1
- package/docs/architecture/current/implementation-status.md +1 -1
- package/docs/architecture/current/legacy-governance.md +58 -0
- package/docs/architecture/current/multi-repository-execution.md +1 -1
- package/docs/architecture/current/peer-supercontroller-runtime.md +1 -1
- package/docs/architecture/current/personal-assistant-plugin-baseline.md +4 -5
- package/docs/architecture/current/scheduler-and-resource-claims.md +0 -10
- package/docs/architecture/current/target-requirements-traceability.md +2 -2
- package/docs/architecture/current/thin-harness-governance-policy.md +1 -1
- package/docs/architecture/current/thin-harness-v1.md +9 -11
- package/docs/architecture/domains/runtime-harness.md +1 -1
- package/docs/forge-autonomous-goal-loop.md +3 -1
- package/docs/forge-chatgpt-mcp-setup.md +51 -184
- package/docs/images/forge-banner-cn.svg +26 -1
- package/docs/images/forge-banner.svg +26 -1
- package/docs/operations/features.md +1 -1
- package/docs/operations/homebrew.md +3 -3
- package/docs/operations/mcp-tool-exposure.md +5 -5
- package/docs/operations/platform-support.md +45 -37
- package/docs/operations/platform-support.zh-CN.md +40 -38
- package/docs/operations/releasing.md +3 -3
- package/docs/operations/releasing.zh-CN.md +3 -3
- package/docs/operations/troubleshooting.md +11 -6
- package/docs/operations/troubleshooting.zh-CN.md +11 -6
- package/docs/public-usage-guide.md +28 -21
- package/docs/public-usage-guide.zh-CN.md +28 -21
- package/docs/tutorials/01-install-and-start.md +56 -35
- package/docs/tutorials/01-install-and-start.zh-CN.md +56 -35
- package/docs/tutorials/02-connect-chatgpt.md +112 -46
- package/docs/tutorials/02-connect-chatgpt.zh-CN.md +113 -45
- package/docs/wiki/Architecture.md +1 -1
- package/docs/wiki/Installation.md +2 -2
- package/docs/wiki/Quick-Start.md +1 -1
- package/docs/wiki/Work-Lifecycle.md +1 -1
- package/install.ps1 +4 -5
- package/install.sh +4 -6
- package/package.json +15 -5
- package/scripts/archive-workflow.sh +0 -1
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +27 -14
- package/scripts/build-controller-ui.ts +28 -0
- package/scripts/capture-plan.sh +4 -4
- package/scripts/check-agent-tooling.sh +12 -1
- package/scripts/check-platform-support.mjs +2 -2
- package/scripts/check-public-docs.sh +16 -5
- package/scripts/check-release-readiness.sh +5 -11
- package/scripts/check-runtime-architecture.mjs +14 -22
- package/scripts/check-tarball-install-smoke.sh +3 -2
- package/scripts/check-task-workflow.sh +2 -2
- package/scripts/codex-handoff-resume.sh +5 -5
- package/scripts/create-project-dirs.sh +3 -3
- package/scripts/ensure-task-workflow.sh +45 -35
- package/scripts/init-project.sh +4 -4
- package/scripts/install-desktop-operator-registration.ts +2 -2
- package/scripts/lib/project-init-lib.sh +46 -44
- package/scripts/migrate-project-template.sh +7 -4
- package/scripts/new-plan.sh +1 -1
- package/scripts/plan-to-todo.sh +2 -2
- package/scripts/prepare-codex-handoff.sh +2 -2
- package/scripts/prepare-handoff.sh +9 -9
- package/scripts/refresh-current-status.sh +2 -2
- package/scripts/setup-plugins.sh +3 -3
- package/scripts/ship-worktrees.sh +1 -1
- package/scripts/sprint-backlog.sh +5 -5
- package/scripts/sync-codex-installed-copies.sh +2 -3
- package/scripts/verify-sprint.sh +7 -7
- package/scripts/workstream-sync.sh +1 -1
- package/src/cli/chatgpt-browser/bridge-extension.ts +1 -1
- package/src/cli/chatgpt-browser/bridge-provider.ts +29 -0
- package/src/cli/chatgpt-browser/file-policy.ts +3 -1
- package/src/cli/commands/chatgpt.ts +51 -0
- package/src/cli/commands/doctor.ts +1 -1
- package/src/cli/commands/init-hook.ts +211 -70
- package/src/cli/commands/init.ts +5 -6
- package/src/cli/commands/mcp.ts +29 -5
- package/src/cli/commands/runtime.ts +60 -8
- package/src/cli/commands/setup-profile.ts +441 -0
- package/src/cli/controller/check-runner.ts +20 -3
- package/src/cli/controller/codex-continuation.ts +4 -7
- package/src/cli/controller/context-pack.ts +118 -14
- package/src/cli/controller/operational-plan.ts +4 -4
- package/src/cli/controller/task-ledger.ts +10 -10
- package/src/cli/controller/work-mode.ts +6 -13
- package/src/cli/index.ts +14 -35
- package/src/cli/local-bridge/connector-freshness.ts +1 -1
- package/src/cli/local-bridge/console-automations.ts +160 -0
- package/src/cli/local-bridge/dashboard.ts +8 -1303
- package/src/cli/local-bridge/facade-api.ts +16 -7
- package/src/cli/local-bridge/frontend/src/App.tsx +10 -0
- package/src/cli/local-bridge/frontend/src/components/Button.tsx +1 -0
- package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +3 -0
- package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +1 -0
- package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +1 -0
- package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +1 -0
- package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +3 -0
- package/src/cli/local-bridge/frontend/src/design/icons.tsx +11 -0
- package/src/cli/local-bridge/frontend/src/design/tokens.css +8 -0
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +3 -0
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +4 -0
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +3 -0
- package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +2 -0
- package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +2 -0
- package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +2 -0
- package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +3 -0
- package/src/cli/local-bridge/frontend/src/lib/api.ts +23 -0
- package/src/cli/local-bridge/frontend/src/lib/format.ts +4 -0
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +7 -0
- package/src/cli/local-bridge/frontend/src/main.tsx +1 -0
- package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +1 -0
- package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +2 -0
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +3 -0
- package/src/cli/local-bridge/frontend/src/styles.css +12 -0
- package/src/cli/local-bridge/frontend/src/types.ts +14 -0
- package/src/cli/local-bridge/frontend/src/vite-env.d.ts +1 -0
- package/src/cli/local-bridge/frontend/vite.config.ts +27 -0
- package/src/cli/local-bridge/server.ts +44 -0
- package/src/cli/local-bridge/ui-dist/app.css +1 -0
- package/src/cli/local-bridge/ui-dist/app.js +11094 -0
- package/src/cli/mcp/instructions.ts +3 -3
- package/src/cli/mcp/legacy-tool-service.ts +27 -23
- package/src/cli/mcp/multi-repository.ts +2 -0
- package/src/cli/mcp/policy.ts +7 -5
- package/src/cli/mcp/repository-tools.ts +53 -14
- package/src/cli/mcp/server.ts +5 -10
- package/src/cli/mcp/setup.ts +211 -275
- package/src/cli/mcp/tool-capability-cache.ts +1 -1
- package/src/cli/mcp/toolset-names.ts +1 -1
- package/src/cli/mcp/toolset.ts +0 -1
- package/src/cli/mcp/transports/http.ts +24 -4
- package/src/cli/repositories/branch-name-policy.ts +0 -7
- package/src/cli/repositories/controller-home.ts +110 -3
- package/src/cli/repositories/safe-patch.ts +1 -1
- package/src/cli/repositories/selected-path-actions.ts +26 -24
- package/src/core/adoption/gitignore-plan.ts +2 -3
- package/src/core/adoption/plan.ts +4 -1
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +556 -0
- package/src/runtime/control-plane/execution/work-handle-store.ts +128 -1
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +277 -0
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +169 -0
- package/src/runtime/control-plane/facade/capability-registry.ts +0 -10
- package/src/runtime/control-plane/facade/handoff-inbox-store.ts +44 -25
- package/src/runtime/control-plane/facade/suggested-actions.ts +7 -1
- package/src/runtime/control-plane/facade/types.ts +0 -1
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +30 -8
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +114 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +175 -0
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +161 -0
- package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +64 -0
- package/src/runtime/control-plane/launcher/thin-launcher.ts +117 -24
- package/src/runtime/control-plane/routing/route-policy.ts +14 -14
- package/src/runtime/control-plane/runtime-cleanup.ts +0 -1
- package/src/runtime/evidence/event-ledger.ts +1 -1
- package/src/runtime/{workflow/campaigns/workspace.ts → execution/managed-workspace.ts} +41 -43
- package/src/runtime/execution/process-runtime/check-facade.ts +6 -0
- package/src/runtime/execution/process-runtime/check-result.ts +45 -0
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +42 -16
- package/src/runtime/execution/process-runtime/command-facade.ts +4 -2
- package/src/runtime/execution/process-runtime/gc.ts +1 -1
- package/src/runtime/execution/process-runtime/index.ts +3 -0
- package/src/runtime/execution/process-runtime/interactive-admission.ts +14 -5
- package/src/runtime/execution/process-runtime/persisted-check.ts +285 -0
- package/src/runtime/execution/process-runtime/resource-claims.ts +30 -0
- package/src/runtime/execution/process-runtime/runtime.ts +18 -1
- package/src/runtime/execution/process-runtime/store.ts +1 -19
- package/src/runtime/execution/process-runtime/types.ts +4 -0
- package/src/runtime/execution/thin-harness/execution-router.ts +0 -1
- package/src/runtime/execution/thin-harness/fast-executor.ts +1 -1
- package/src/runtime/execution/thin-harness/lightweight-lanes.ts +1 -2
- package/src/runtime/execution/thin-harness/types.ts +0 -1
- package/src/runtime/execution/workers/executor.ts +1 -1
- package/src/runtime/gateway/mcp/execution-tools.ts +1 -1
- package/src/runtime/gateway/mcp/persisted-check-process.ts +3 -255
- package/src/runtime/gateway/mcp/process-tools.ts +16 -3
- package/src/runtime/gateway/mcp/resource-policy.ts +0 -9
- package/src/runtime/gateway/mcp/router.ts +117 -182
- package/src/runtime/gateway/mcp/runtime-tools.ts +331 -409
- package/src/runtime/gateway/mcp/work-validation-reconciler.ts +1 -219
- package/src/runtime/plugins/browser-adapter.ts +5 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +1 -1
- package/src/runtime/plugins/store.ts +14 -6
- package/src/runtime/plugins/types.ts +1 -1
- package/src/runtime/projections/materialized-view.ts +1 -23
- package/src/runtime/recovery/maintenance-executor.ts +9 -0
- package/src/runtime/resources/ownership.ts +0 -1
- package/src/runtime/root/gateway-adapter.ts +2 -0
- package/src/runtime/root/mcp-transport.ts +10 -2
- package/src/runtime/root/package-runtime-release.ts +161 -0
- package/src/runtime/root/package-runtime-service.ts +172 -0
- package/src/runtime/root/runtime.ts +2 -1
- package/src/runtime/root/scheduler.ts +5 -0
- package/src/runtime/root/service.ts +18 -5
- package/src/runtime/standalone-recovery/core.ts +65 -16
- package/src/runtime/workflow/findings/store.ts +145 -106
- package/src/runtime/workflow/findings/types.ts +22 -0
- package/src/runtime/workflow/schedules/engine.ts +132 -2
- package/src/runtime/workflow/schedules/pending-activation.ts +139 -0
- package/src/runtime/workflow/schedules/work-continuation.ts +218 -0
- package/docs/CHANGELOG.md +0 -978
- package/docs/README.zh-CN.md +0 -58
- package/scripts/smoke-chatgpt-supervised-automation.ts +0 -208
- package/src/cli/commands/controller.ts +0 -680
- package/src/runtime/workflow/campaigns/cleanup.ts +0 -263
- package/src/runtime/workflow/campaigns/engine.ts +0 -112
- package/src/runtime/workflow/campaigns/normalize.ts +0 -100
- package/src/runtime/workflow/campaigns/review.ts +0 -315
- package/src/runtime/workflow/campaigns/store.ts +0 -526
- package/src/runtime/workflow/campaigns/supervisor.ts +0 -112
- package/src/runtime/workflow/campaigns/types.ts +0 -290
- /package/src/runtime/{workflow/campaigns → execution}/workspace-agent.ts +0 -0
|
@@ -22,7 +22,7 @@ export function controllerCapabilityGroupForTool(name: string): ControllerCapabi
|
|
|
22
22
|
if (name.startsWith('plugin_') || name === 'list_plugins') return 'plugins';
|
|
23
23
|
if (name.startsWith('runtime_') || name.startsWith('controller_')) return 'runtime';
|
|
24
24
|
if (name.startsWith('work_') || name.startsWith('session_') || name.startsWith('approval_') || name.startsWith('result_')) return 'workflow';
|
|
25
|
-
if (name.startsWith('admin_')
|
|
25
|
+
if (name.startsWith('admin_')) return 'admin';
|
|
26
26
|
return 'legacy';
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -6,7 +6,7 @@ export const PREFERRED_FACADE_TOOL_NAMES = [...FACADE_TOOLS] as const;
|
|
|
6
6
|
/**
|
|
7
7
|
* Default ChatGPT-facing MCP surface. The Runtime keeps every internal atomic
|
|
8
8
|
* handler and the full compatibility profile; ChatGPT only needs this bounded
|
|
9
|
-
* execution surface. Atomic Git / issue-task /
|
|
9
|
+
* execution surface. Atomic Git / issue-task / edit-session / plugin
|
|
10
10
|
* tools remain registered and reachable through `toolset=full`.
|
|
11
11
|
*/
|
|
12
12
|
export const DEFAULT_CONTROLLER_TOOL_NAMES = [
|
package/src/cli/mcp/toolset.ts
CHANGED
|
@@ -213,7 +213,6 @@ function staticControllerExposureSnapshot(
|
|
|
213
213
|
function capabilityForToolName(name: string): string {
|
|
214
214
|
if (name.startsWith('rh_')) return 'facade';
|
|
215
215
|
if (name.startsWith('git_') || name.startsWith('repository_git_')) return 'git';
|
|
216
|
-
if (name.includes('campaign')) return 'campaign';
|
|
217
216
|
if (/(^|_)(issue|task|project|worklog)(_|$)/.test(name)) return 'issue-task';
|
|
218
217
|
if (name.startsWith('repository_') || name.startsWith('search_') || name.startsWith('read_')) return 'repository';
|
|
219
218
|
if (name.startsWith('ios_')) return 'ios';
|
|
@@ -162,6 +162,18 @@ export function mcpSessionToolSurfaceFingerprintIsCurrent(
|
|
|
162
162
|
return !sessionFingerprint || !currentFingerprint || sessionFingerprint === currentFingerprint;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
export async function resolveMcpSessionCurrentFingerprint(
|
|
166
|
+
publishedFingerprint: string | undefined,
|
|
167
|
+
loadRuntimeFingerprint?: () => Promise<string | undefined>,
|
|
168
|
+
): Promise<string | undefined> {
|
|
169
|
+
// Runtime status publishes the exact schema fingerprint observed at session
|
|
170
|
+
// initialization/cutover. Prefer that O(1) fence on the hot path. If the
|
|
171
|
+
// publication is temporarily unavailable, retain the previous fail-safe
|
|
172
|
+
// behavior by asking the Canonical Runtime directly.
|
|
173
|
+
if (publishedFingerprint) return publishedFingerprint;
|
|
174
|
+
return await loadRuntimeFingerprint?.();
|
|
175
|
+
}
|
|
176
|
+
|
|
165
177
|
function toolCallOutsideSessionSchema(body: unknown, toolNames: readonly string[] | undefined): boolean {
|
|
166
178
|
if (!toolNames) return false;
|
|
167
179
|
const messages = Array.isArray(body) ? body : [body];
|
|
@@ -744,8 +756,12 @@ async function handleMcpPost(
|
|
|
744
756
|
registry.beginPost(sessionId);
|
|
745
757
|
stats.activePosts += 1;
|
|
746
758
|
try {
|
|
747
|
-
const
|
|
748
|
-
|
|
759
|
+
const currentFingerprint = await resolveMcpSessionCurrentFingerprint(
|
|
760
|
+
currentToolSurfaceFingerprint(),
|
|
761
|
+
resolveRuntimeSchema
|
|
762
|
+
? async () => (await resolveRuntimeSchema(managed.toolContext))?.fingerprint
|
|
763
|
+
: undefined,
|
|
764
|
+
);
|
|
749
765
|
if (!mcpSessionToolSurfaceFingerprintIsCurrent(managed.toolSurfaceFingerprint, currentFingerprint)) {
|
|
750
766
|
// Keep the transport/session alive long enough for the host to observe
|
|
751
767
|
// the recoverable reset and issue a replacement initialize request.
|
|
@@ -795,8 +811,12 @@ async function handleMcpGet(
|
|
|
795
811
|
req.once('aborted', releaseStream);
|
|
796
812
|
res.once('close', releaseStream);
|
|
797
813
|
try {
|
|
798
|
-
const
|
|
799
|
-
|
|
814
|
+
const currentFingerprint = await resolveMcpSessionCurrentFingerprint(
|
|
815
|
+
currentToolSurfaceFingerprint(),
|
|
816
|
+
resolveRuntimeSchema
|
|
817
|
+
? async () => (await resolveRuntimeSchema(managed.toolContext))?.fingerprint
|
|
818
|
+
: undefined,
|
|
819
|
+
);
|
|
800
820
|
if (!mcpSessionToolSurfaceFingerprintIsCurrent(managed.toolSurfaceFingerprint, currentFingerprint)) {
|
|
801
821
|
// Preserve the existing SSE transport while asking the host to
|
|
802
822
|
// reinitialize. The replacement initialize owns supersession/cleanup.
|
|
@@ -48,10 +48,3 @@ export function validateBranchName(raw: unknown, options: BranchNamePolicyOption
|
|
|
48
48
|
export function branchRef(branch: unknown, options: BranchNamePolicyOptions = {}): string {
|
|
49
49
|
return `refs/heads/${validateBranchName(branch, options)}`;
|
|
50
50
|
}
|
|
51
|
-
|
|
52
|
-
export function campaignBranchName(input: { readonly title: string; readonly identity: string }): string {
|
|
53
|
-
return validateBranchName(
|
|
54
|
-
`campaign/${branchSlugSegment(input.title)}-${input.identity}`,
|
|
55
|
-
{ purpose: 'CAMPAIGN_WORKSPACE_BRANCH' },
|
|
56
|
-
);
|
|
57
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { existsSync, mkdirSync } from 'fs';
|
|
1
|
+
import { existsSync, lstatSync, mkdirSync, readlinkSync, renameSync, rmSync, symlinkSync } from 'fs';
|
|
2
2
|
import { homedir } from 'os';
|
|
3
|
-
import { join, resolve } from 'path';
|
|
3
|
+
import { basename, dirname, join, relative, resolve } from 'path';
|
|
4
4
|
|
|
5
5
|
export function resolveControllerHome(explicit?: string): string {
|
|
6
6
|
const configured = explicit?.trim()
|
|
@@ -29,8 +29,115 @@ export function resolveRepoPreferredControllerHome(repoRoot?: string, explicit?:
|
|
|
29
29
|
return resolveControllerHome();
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
export function repoLocalNoIndexControllerHome(
|
|
33
|
+
controllerHome: string,
|
|
34
|
+
platform: NodeJS.Platform = process.platform,
|
|
35
|
+
): string | undefined {
|
|
36
|
+
const home = resolve(controllerHome);
|
|
37
|
+
if (platform !== 'darwin' || basename(home) !== 'controller-home' || basename(dirname(home)) !== '_ops') return undefined;
|
|
38
|
+
return `${home}.noindex`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function ensureControllerHomeStorage(
|
|
42
|
+
controllerHome: string,
|
|
43
|
+
platform: NodeJS.Platform = process.platform,
|
|
44
|
+
): string {
|
|
45
|
+
const home = resolve(controllerHome);
|
|
46
|
+
const physical = repoLocalNoIndexControllerHome(home, platform);
|
|
47
|
+
if (!physical || existsSync(home)) return home;
|
|
48
|
+
|
|
49
|
+
let logicalEntryExists = false;
|
|
50
|
+
try { logicalEntryExists = lstatSync(home).isSymbolicLink(); } catch { /* absent */ }
|
|
51
|
+
mkdirSync(physical, { recursive: true });
|
|
52
|
+
if (!logicalEntryExists) {
|
|
53
|
+
const target = relative(dirname(home), physical) || basename(physical);
|
|
54
|
+
try {
|
|
55
|
+
symlinkSync(target, home, 'dir');
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (!existsSync(home)) throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return home;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ControllerHomeStorageMigration {
|
|
64
|
+
migrated: boolean;
|
|
65
|
+
logicalHome: string;
|
|
66
|
+
physicalHome?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function symlinkTargetPath(path: string): string {
|
|
70
|
+
return resolve(dirname(path), readlinkSync(path));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function repoLocalControllerHomeStorageNeedsMigration(
|
|
74
|
+
controllerHome: string,
|
|
75
|
+
platform: NodeJS.Platform = process.platform,
|
|
76
|
+
): boolean {
|
|
77
|
+
const logicalHome = resolve(controllerHome);
|
|
78
|
+
const physicalHome = repoLocalNoIndexControllerHome(logicalHome, platform);
|
|
79
|
+
if (!physicalHome) return false;
|
|
80
|
+
let stat;
|
|
81
|
+
try {
|
|
82
|
+
stat = lstatSync(logicalHome);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false;
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
if (stat.isSymbolicLink()) {
|
|
88
|
+
if (symlinkTargetPath(logicalHome) !== physicalHome) {
|
|
89
|
+
throw new Error(`CONTROLLER_HOME_NOINDEX_SYMLINK_CONFLICT: ${logicalHome}`);
|
|
90
|
+
}
|
|
91
|
+
if (!existsSync(physicalHome)) {
|
|
92
|
+
throw new Error(`CONTROLLER_HOME_NOINDEX_TARGET_MISSING: ${physicalHome}`);
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (!stat.isDirectory()) throw new Error(`CONTROLLER_HOME_STORAGE_UNSUPPORTED: ${logicalHome}`);
|
|
97
|
+
if (existsSync(physicalHome)) throw new Error(`CONTROLLER_HOME_NOINDEX_TARGET_EXISTS: ${physicalHome}`);
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Caller must prove the Canonical Runtime is fully stopped before invoking. */
|
|
102
|
+
export function migrateStoppedRepoLocalControllerHomeStorage(
|
|
103
|
+
controllerHome: string,
|
|
104
|
+
platform: NodeJS.Platform = process.platform,
|
|
105
|
+
): ControllerHomeStorageMigration {
|
|
106
|
+
const logicalHome = resolve(controllerHome);
|
|
107
|
+
const physicalHome = repoLocalNoIndexControllerHome(logicalHome, platform);
|
|
108
|
+
if (!physicalHome || !repoLocalControllerHomeStorageNeedsMigration(logicalHome, platform)) {
|
|
109
|
+
return { migrated: false, logicalHome, physicalHome };
|
|
110
|
+
}
|
|
111
|
+
renameSync(logicalHome, physicalHome);
|
|
112
|
+
try {
|
|
113
|
+
const target = relative(dirname(logicalHome), physicalHome) || basename(physicalHome);
|
|
114
|
+
symlinkSync(target, logicalHome, 'dir');
|
|
115
|
+
if (!lstatSync(logicalHome).isSymbolicLink() || symlinkTargetPath(logicalHome) !== physicalHome || !existsSync(physicalHome)) {
|
|
116
|
+
throw new Error(`CONTROLLER_HOME_NOINDEX_POSTCHECK_FAILED: ${logicalHome}`);
|
|
117
|
+
}
|
|
118
|
+
return { migrated: true, logicalHome, physicalHome };
|
|
119
|
+
} catch (error) {
|
|
120
|
+
try { rmSync(logicalHome, { force: true }); } catch { /* best effort */ }
|
|
121
|
+
if (!existsSync(logicalHome) && existsSync(physicalHome)) renameSync(physicalHome, logicalHome);
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Caller must prove the Canonical Runtime is fully stopped before invoking. */
|
|
127
|
+
export function rollbackStoppedRepoLocalControllerHomeStorage(migration: ControllerHomeStorageMigration): void {
|
|
128
|
+
if (!migration.migrated || !migration.physicalHome) return;
|
|
129
|
+
const { logicalHome, physicalHome } = migration;
|
|
130
|
+
const stat = lstatSync(logicalHome);
|
|
131
|
+
if (!stat.isSymbolicLink() || symlinkTargetPath(logicalHome) !== physicalHome) {
|
|
132
|
+
throw new Error(`CONTROLLER_HOME_NOINDEX_ROLLBACK_CONFLICT: ${logicalHome}`);
|
|
133
|
+
}
|
|
134
|
+
if (!existsSync(physicalHome)) throw new Error(`CONTROLLER_HOME_NOINDEX_ROLLBACK_TARGET_MISSING: ${physicalHome}`);
|
|
135
|
+
rmSync(logicalHome, { force: true });
|
|
136
|
+
renameSync(physicalHome, logicalHome);
|
|
137
|
+
}
|
|
138
|
+
|
|
32
139
|
export function ensureControllerHome(explicit?: string): string {
|
|
33
|
-
const home = resolveControllerHome(explicit);
|
|
140
|
+
const home = ensureControllerHomeStorage(resolveControllerHome(explicit));
|
|
34
141
|
for (const child of ['', 'repositories', 'system', 'locks', 'indexes', 'audit', 'mcp', 'sessions', 'work-handles']) {
|
|
35
142
|
mkdirSync(join(home, child), { recursive: true });
|
|
36
143
|
}
|
|
@@ -415,7 +415,7 @@ export function applySafePatch(repository: RepositoryRecord, input: {
|
|
|
415
415
|
failures,
|
|
416
416
|
plan,
|
|
417
417
|
next: status === 'applied'
|
|
418
|
-
? 'Run
|
|
418
|
+
? 'Review the returned edit-session diff evidence. Run focused validation only after the coherent edit batch is stable, then commit when acceptance evidence is sufficient.'
|
|
419
419
|
: 'Inspect failures[].context, refresh the operation text, and re-run only the failed chunk or remaining operations.',
|
|
420
420
|
};
|
|
421
421
|
}
|
|
@@ -31,13 +31,13 @@ export interface SelectedPathCommitResult {
|
|
|
31
31
|
error?: { code: string; message: string };
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export interface
|
|
34
|
+
export interface TransferArtifactPreview {
|
|
35
35
|
path: string;
|
|
36
36
|
exists: boolean;
|
|
37
37
|
preview?: string;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export interface
|
|
40
|
+
export interface PreparedTransferArtifacts {
|
|
41
41
|
reason: string;
|
|
42
42
|
usedScript: boolean;
|
|
43
43
|
fallbackUsed: boolean;
|
|
@@ -45,7 +45,7 @@ export interface PreparedHandoffArtifacts {
|
|
|
45
45
|
exitCode?: number;
|
|
46
46
|
stdout?: string;
|
|
47
47
|
stderr?: string;
|
|
48
|
-
artifacts:
|
|
48
|
+
artifacts: TransferArtifactPreview[];
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
function boundedOutputBytes(value: number | undefined): number {
|
|
@@ -129,7 +129,7 @@ function stagedPaths(repository: RepositoryRecord, paths: string[]): string[] {
|
|
|
129
129
|
.filter(Boolean);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
function artifactPreview(repository: RepositoryRecord, path: string):
|
|
132
|
+
function artifactPreview(repository: RepositoryRecord, path: string): TransferArtifactPreview {
|
|
133
133
|
const absolute = join(repository.canonicalRoot, path);
|
|
134
134
|
if (!existsSync(absolute)) return { path, exists: false };
|
|
135
135
|
const content = redactProcessOutput(readFileSync(absolute, 'utf-8'));
|
|
@@ -147,7 +147,7 @@ function repositoryChangedPaths(repository: RepositoryRecord): string[] {
|
|
|
147
147
|
return [...new Set([tracked, staged, untracked].join('\n').split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean))].sort();
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
function
|
|
150
|
+
function writePatchTransferArtifact(repository: RepositoryRecord, transferDir: string, reason: string): TransferArtifactPreview {
|
|
151
151
|
const diff = runGit(repository, ['diff', '--binary'], 512 * 1024).stdout;
|
|
152
152
|
const stagedDiff = runGit(repository, ['diff', '--cached', '--binary'], 512 * 1024).stdout;
|
|
153
153
|
const touchedPaths = repositoryChangedPaths(repository);
|
|
@@ -158,27 +158,29 @@ function writePatchHandoffArtifact(repository: RepositoryRecord, handoffDir: str
|
|
|
158
158
|
touchedPaths,
|
|
159
159
|
checks: [],
|
|
160
160
|
actor: 'forge',
|
|
161
|
-
source: `
|
|
161
|
+
source: `patch-transfer:${reason}`,
|
|
162
162
|
notes: [
|
|
163
163
|
'Integration must use selected-path review gates.',
|
|
164
164
|
'Do not overwrite unrelated dirty files; rerun conflict detection before applying this patch.',
|
|
165
165
|
],
|
|
166
166
|
});
|
|
167
|
-
const patchPath = join(
|
|
167
|
+
const patchPath = join(transferDir, 'patch.json');
|
|
168
168
|
writeFileSync(patchPath, `${JSON.stringify(artifact, null, 2)}\n`, 'utf-8');
|
|
169
|
-
return artifactPreview(repository, '.ai/harness/
|
|
169
|
+
return artifactPreview(repository, '.ai/harness/transfers/patch.json');
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
function ensureFallbackArtifact(repository: RepositoryRecord, reason: string):
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
172
|
+
function ensureFallbackArtifact(repository: RepositoryRecord, reason: string): TransferArtifactPreview {
|
|
173
|
+
const transferDir = join(repository.canonicalRoot, '.ai', 'harness', 'transfers');
|
|
174
|
+
const sessionDir = join(repository.canonicalRoot, '.ai', 'harness', 'session');
|
|
175
|
+
mkdirSync(transferDir, { recursive: true });
|
|
176
|
+
mkdirSync(sessionDir, { recursive: true });
|
|
177
|
+
const patchArtifact = writePatchTransferArtifact(repository, transferDir, reason);
|
|
176
178
|
const timestamp = new Date().toISOString();
|
|
177
|
-
const currentPath = join(
|
|
178
|
-
const resumePath = join(
|
|
179
|
+
const currentPath = join(sessionDir, 'continuation.md');
|
|
180
|
+
const resumePath = join(sessionDir, 'resume.md');
|
|
179
181
|
if (!existsSync(currentPath)) {
|
|
180
182
|
writeFileSync(currentPath, [
|
|
181
|
-
'#
|
|
183
|
+
'# Forge Session Continuation Snapshot',
|
|
182
184
|
'',
|
|
183
185
|
`> **Generated**: ${timestamp}`,
|
|
184
186
|
`> **Reason**: ${reason}`,
|
|
@@ -187,7 +189,7 @@ function ensureFallbackArtifact(repository: RepositoryRecord, reason: string): H
|
|
|
187
189
|
'## Exact Next Step',
|
|
188
190
|
'',
|
|
189
191
|
'- Inspect the selected-path Git diff and continue from this repository state.',
|
|
190
|
-
'- Review `.ai/harness/
|
|
192
|
+
'- Review `.ai/harness/transfers/patch.json` before any integration attempt.',
|
|
191
193
|
'',
|
|
192
194
|
].join('\n'), 'utf-8');
|
|
193
195
|
}
|
|
@@ -197,8 +199,8 @@ function ensureFallbackArtifact(repository: RepositoryRecord, reason: string): H
|
|
|
197
199
|
'',
|
|
198
200
|
`- Repository: \`${repository.canonicalRoot}\``,
|
|
199
201
|
`- Reason: \`${reason}\``,
|
|
200
|
-
'- Patch artifact: `.ai/harness/
|
|
201
|
-
'- Next: open `.ai/harness/
|
|
202
|
+
'- Patch artifact: `.ai/harness/transfers/patch.json`',
|
|
203
|
+
'- Next: open `.ai/harness/session/continuation.md` and continue from the recorded state.',
|
|
202
204
|
'',
|
|
203
205
|
].join('\n'), 'utf-8');
|
|
204
206
|
}
|
|
@@ -290,10 +292,10 @@ export function commitSelectedPaths(
|
|
|
290
292
|
};
|
|
291
293
|
}
|
|
292
294
|
|
|
293
|
-
export function
|
|
295
|
+
export function prepareTransferArtifacts(
|
|
294
296
|
repository: RepositoryRecord,
|
|
295
297
|
input: { reason?: unknown },
|
|
296
|
-
):
|
|
298
|
+
): PreparedTransferArtifacts {
|
|
297
299
|
const reason = String(input.reason ?? 'manual').trim() || 'manual';
|
|
298
300
|
const script = join(repository.canonicalRoot, 'scripts', 'prepare-handoff.sh');
|
|
299
301
|
const usedScript = existsSync(script);
|
|
@@ -321,8 +323,8 @@ export function prepareFallbackHandoffArtifacts(
|
|
|
321
323
|
ok = exitCode === 0 && !result.error;
|
|
322
324
|
}
|
|
323
325
|
|
|
324
|
-
const beforeCurrent = existsSync(join(repository.canonicalRoot, '.ai', 'harness', '
|
|
325
|
-
const beforeResume = existsSync(join(repository.canonicalRoot, '.ai', 'harness', '
|
|
326
|
+
const beforeCurrent = existsSync(join(repository.canonicalRoot, '.ai', 'harness', 'session', 'continuation.md'));
|
|
327
|
+
const beforeResume = existsSync(join(repository.canonicalRoot, '.ai', 'harness', 'session', 'resume.md'));
|
|
326
328
|
const patchArtifact = ensureFallbackArtifact(repository, reason);
|
|
327
329
|
const fallbackUsed = !usedScript || !ok || !beforeCurrent || !beforeResume;
|
|
328
330
|
|
|
@@ -333,8 +335,8 @@ export function prepareFallbackHandoffArtifacts(
|
|
|
333
335
|
ok: usedScript ? ok : true,
|
|
334
336
|
...(usedScript ? { exitCode, stdout, stderr } : {}),
|
|
335
337
|
artifacts: [
|
|
336
|
-
artifactPreview(repository, '.ai/harness/
|
|
337
|
-
artifactPreview(repository, '.ai/harness/
|
|
338
|
+
artifactPreview(repository, '.ai/harness/session/continuation.md'),
|
|
339
|
+
artifactPreview(repository, '.ai/harness/session/resume.md'),
|
|
338
340
|
patchArtifact,
|
|
339
341
|
],
|
|
340
342
|
};
|
|
@@ -39,7 +39,6 @@ export const GITIGNORE_MANAGED_BLOCK_CONTENT = [
|
|
|
39
39
|
".claude/.trace.jsonl",
|
|
40
40
|
".claude/.session-handoff.md",
|
|
41
41
|
".claude/.task-state.json",
|
|
42
|
-
".claude/.task-handoff.md",
|
|
43
42
|
".claude/.codegraph-state/",
|
|
44
43
|
".claude/*.tmp",
|
|
45
44
|
".claude/*.bak",
|
|
@@ -52,8 +51,8 @@ export const GITIGNORE_MANAGED_BLOCK_CONTENT = [
|
|
|
52
51
|
".ai/harness/archive/",
|
|
53
52
|
".ai/harness/backups/",
|
|
54
53
|
".ai/harness/failures/latest.jsonl",
|
|
55
|
-
".ai/harness/
|
|
56
|
-
".ai/harness/
|
|
54
|
+
".ai/harness/session/continuation.md",
|
|
55
|
+
".ai/harness/session/resume.md",
|
|
57
56
|
".ai/harness/capability-context/",
|
|
58
57
|
".ai/harness/security/*",
|
|
59
58
|
"!.ai/harness/security/.gitkeep",
|
|
@@ -26,7 +26,10 @@ const MINIMAL_DIRS = [
|
|
|
26
26
|
"tasks/notes",
|
|
27
27
|
"docs",
|
|
28
28
|
".ai/harness/checks",
|
|
29
|
-
".ai/harness/
|
|
29
|
+
".ai/harness/session",
|
|
30
|
+
".ai/harness/controller/packets",
|
|
31
|
+
".ai/harness/projections",
|
|
32
|
+
".ai/harness/transfers",
|
|
30
33
|
] as const;
|
|
31
34
|
|
|
32
35
|
const STANDARD_EXTRA_DIRS = [
|