@moretea-labs/forge 1.7.0 → 1.7.1
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 +2 -0
- package/CHANGELOG.md +13 -0
- package/CLAUDE.md +2 -0
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/agents/openai.yaml +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +2 -0
- package/assets/plugin-registry.v1.json +3 -3
- package/assets/skill-version.json +2 -2
- package/assets/templates/helpers/check-architecture-sync.sh +17 -0
- package/assets/templates/helpers/inspect-project-state.ts +8 -6
- package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
- package/assets/templates/helpers/workflow-contract.ts +5 -1
- package/docs/architecture/CURRENT.md +12 -4
- package/docs/forge-plugin-management.md +1 -1
- package/docs/operations/controller-performance-and-502.md +19 -0
- package/docs/operations/homebrew.md +2 -2
- package/package.json +5 -2
- package/scripts/architecture-event.ts +2 -820
- package/scripts/architecture-queue.sh +2 -645
- package/scripts/archive-architecture-request.sh +2 -242
- package/scripts/archive-workflow.sh +1 -291
- package/scripts/benchmark-work-efficiency.ts +0 -0
- package/scripts/capability-config.ts +2 -392
- package/scripts/capability-resolver.ts +2 -503
- package/scripts/capture-plan.sh +1 -332
- package/scripts/check-agent-tooling.sh +2 -1531
- package/scripts/check-architecture-sync.sh +2 -302
- package/scripts/check-brain-manifest.sh +2 -253
- package/scripts/check-context-files.sh +2 -47
- package/scripts/check-deploy-sql-order.sh +2 -115
- package/scripts/check-runtime-architecture.mjs +243 -16
- package/scripts/check-skill-version.ts +3 -191
- package/scripts/check-source-duplication.mjs +61 -0
- package/scripts/check-stable-baseline.ts +0 -0
- package/scripts/check-task-sync.sh +2 -63
- package/scripts/check-task-workflow.sh +2 -1000
- package/scripts/codex-handoff-resume.sh +2 -267
- package/scripts/context-contract-sync.sh +2 -514
- package/scripts/contract-run.ts +2 -463
- package/scripts/contract-worktree.sh +1 -702
- package/scripts/ensure-task-workflow.sh +1 -1192
- package/scripts/inspect-project-state.ts +4 -279
- package/scripts/lib/project-init-lib.sh +7 -2
- package/scripts/lib/run-forge-helper.sh +19 -0
- package/scripts/lib/run-forge-helper.ts +26 -0
- package/scripts/migrate-project-template.sh +1 -33
- package/scripts/migrate-workflow-docs.ts +4 -412
- package/scripts/new-plan.sh +1 -180
- package/scripts/new-spec.sh +1 -47
- package/scripts/new-sprint.sh +1 -46
- package/scripts/plan-to-todo.sh +1 -771
- package/scripts/prepare-codex-handoff.sh +2 -201
- package/scripts/prepare-handoff.sh +1 -78
- package/scripts/refresh-current-status.sh +2 -467
- package/scripts/run-governed-gate.ts +3 -0
- package/scripts/select-agent-context-blocks.sh +2 -64
- package/scripts/ship-worktrees.sh +1 -531
- package/scripts/sprint-backlog.sh +1 -834
- package/scripts/stage-runtime-release.ts +2 -1
- package/scripts/summarize-failures.sh +2 -123
- package/scripts/switch-plan.sh +1 -150
- package/scripts/sync-brain-docs.sh +2 -379
- package/scripts/sync-generated-authority.mjs +175 -0
- package/scripts/verify-contract.sh +2 -593
- package/scripts/verify-sprint.sh +1 -500
- package/scripts/workflow-contract.ts +4 -201
- package/scripts/workstream-sync.sh +2 -261
- package/src/cli/agent-jobs/executor-health.ts +1 -1
- package/src/cli/commands/plugin.ts +12 -3
- package/src/cli/controller/check-runner.ts +7 -2
- package/src/cli/controller/context-pack.ts +46 -21
- package/src/cli/github/contracts.ts +16 -0
- package/src/cli/github/github.ts +2 -16
- package/src/cli/github/session.ts +2 -1
- package/src/cli/local-bridge/facade-api.ts +37 -47
- package/src/cli/local-bridge/server.ts +2 -2
- package/src/cli/mcp/legacy-tool-service.ts +10 -33
- package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
- package/src/cli/mcp/repository-tools.ts +85 -89
- package/src/cli/mcp/server.ts +57 -13
- package/src/cli/mcp/tool-contract.ts +14 -0
- package/src/cli/mcp/toolset.ts +2 -2
- package/src/cli/mcp/transports/http.ts +23 -0
- package/src/cli/repositories/command-classifier.ts +2 -2
- package/src/cli/repositories/command-executor.ts +24 -6
- package/src/cli/repositories/repository-snapshot.ts +5 -2
- package/src/cli/repositories/structured-git.ts +57 -3
- package/src/cli/repository/inspector.ts +11 -3
- package/src/runtime/context/generic-lsp-provider.ts +1 -1
- package/src/runtime/context/semantic-navigation-contract.ts +61 -0
- package/src/runtime/context/semantic-navigation.ts +9 -61
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +18 -25
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
- package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
- package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
- package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
- package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
- package/src/runtime/control-plane/execution/work-handle-authority.ts +96 -0
- package/src/runtime/control-plane/execution/work-handle-store.ts +1 -1
- package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
- package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
- package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
- package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +4 -43
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +13 -3
- package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
- package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
- package/src/runtime/control-plane/facade/controller-round-relay.ts +164 -37
- package/src/runtime/control-plane/facade/controller-session-store.ts +183 -24
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +15 -1
- package/src/runtime/control-plane/facade/goal-workloop.ts +329 -148
- package/src/runtime/control-plane/facade/index.ts +2 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +90 -22
- package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
- package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
- package/src/runtime/control-plane/facade/types.ts +68 -5
- package/src/runtime/control-plane/facade/work-contract-store.ts +3 -209
- package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +14 -7
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +5 -1
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +13 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +15 -6
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +54 -5
- package/src/runtime/control-plane/launcher/thin-launcher.ts +56 -7
- package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
- package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
- package/src/runtime/control-plane/routing/route-policy.ts +18 -6
- package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
- package/src/runtime/evidence/process-check-execution.ts +18 -0
- package/src/runtime/execution/jobs/child-reference.ts +6 -2
- package/src/runtime/execution/process-runtime/check-result.ts +53 -18
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
- package/src/runtime/execution/process-runtime/command-facade.ts +39 -17
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +20 -3
- package/src/runtime/execution/process-runtime/persisted-check.ts +4 -1
- package/src/runtime/execution/process-runtime/runtime.ts +10 -0
- package/src/runtime/execution/process-runtime/types.ts +2 -12
- package/src/runtime/execution/work-activity.ts +21 -0
- package/src/runtime/execution/workers/executor.ts +18 -13
- package/src/runtime/gateway/mcp/execution-tools.ts +35 -2930
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
- package/src/runtime/gateway/mcp/router.ts +2 -2
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +21 -2
- package/src/runtime/gateway/mcp/runtime-tools.ts +805 -1009
- package/src/runtime/plugins/app-store-connect-adapter.ts +93 -28
- package/src/runtime/plugins/browser-adapter.ts +256 -1083
- package/src/runtime/plugins/browser-handoff-host.ts +2 -25
- package/src/runtime/plugins/browser-handoff.ts +18 -3
- package/src/runtime/plugins/browser-macos-bridge.ts +121 -16
- package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
- package/src/runtime/plugins/browser-native-session-service.ts +222 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +2 -0
- package/src/runtime/plugins/browser-runtime.ts +10 -0
- package/src/runtime/plugins/browser-session-store.ts +121 -0
- package/src/runtime/plugins/browser-session-types.ts +98 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +4 -4
- package/src/runtime/plugins/external-adapter.ts +92 -58
- package/src/runtime/plugins/macos-capability-broker.ts +0 -8
- package/src/runtime/plugins/store.ts +66 -9
- package/src/runtime/plugins/types.ts +3 -1
- package/src/runtime/recovery/maintenance-executor.ts +61 -25
- package/src/runtime/root/package-connector-service.ts +46 -4
- package/src/runtime/root/package-runtime-release.ts +1 -1
- package/src/runtime/root/package-runtime-service.ts +33 -2
- package/src/runtime/root/release-manifest.ts +137 -16
- package/src/runtime/root/release-materialize.ts +109 -35
- package/src/runtime/root/types.ts +10 -0
- package/src/runtime/standalone-recovery/core.ts +425 -194
- package/src/runtime/standalone-recovery/entry.ts +1 -1
- package/src/runtime/standalone-recovery/installer.ts +1 -1
- package/src/runtime/workflow/schedules/engine.ts +113 -87
- package/src/runtime/workflow/schedules/settlement.ts +26 -23
- package/src/runtime/workflow/schedules/store.ts +79 -10
|
@@ -274,7 +274,43 @@ export function repositoryGitCommit(controllerHome: string, repository: Reposito
|
|
|
274
274
|
if (diffCheck.status === 0 && input.allowEmpty !== true) {
|
|
275
275
|
return { repoId: repository.repoId, checkoutId: repository.activeCheckoutId, before, stage, after: repositoryGitStatus(repository), committed: false, error: { code: 'GIT_NOTHING_STAGED', message: 'No staged changes to commit. Pass paths to stage, or allow_empty=true for an empty commit.' } };
|
|
276
276
|
}
|
|
277
|
-
const
|
|
277
|
+
const mergeInProgress = runGit(repository, ['rev-parse', '--verify', 'MERGE_HEAD'], 64 * 1024).ok;
|
|
278
|
+
if (mergeInProgress) {
|
|
279
|
+
const unresolved = runGit(repository, ['diff', '--name-only', '--diff-filter=U', '-z'], 128 * 1024);
|
|
280
|
+
const unresolvedPaths = unresolved.stdout.split('\0').map((path) => path.trim()).filter(Boolean);
|
|
281
|
+
if (!unresolved.ok || unresolvedPaths.length > 0) {
|
|
282
|
+
return {
|
|
283
|
+
repoId: repository.repoId,
|
|
284
|
+
checkoutId: repository.activeCheckoutId,
|
|
285
|
+
before,
|
|
286
|
+
stage,
|
|
287
|
+
after: repositoryGitStatus(repository),
|
|
288
|
+
committed: false,
|
|
289
|
+
error: { code: 'GIT_MERGE_UNRESOLVED', message: unresolvedPaths.length > 0 ? `Merge still has unresolved path(s): ${unresolvedPaths.join(', ')}` : unresolved.stderr || 'Unable to inspect unresolved merge paths.' },
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
if (paths.length > 0) {
|
|
293
|
+
const stagedNames = runGit(repository, ['diff', '--cached', '--name-only', '-z'], 128 * 1024);
|
|
294
|
+
if (!stagedNames.ok) {
|
|
295
|
+
return { repoId: repository.repoId, checkoutId: repository.activeCheckoutId, before, stage, after: repositoryGitStatus(repository), committed: false, error: { code: 'GIT_MERGE_STAGED_SCOPE_INSPECTION_FAILED', message: stagedNames.stderr || 'Unable to inspect the merge index.' } };
|
|
296
|
+
}
|
|
297
|
+
const allowed = new Set(paths);
|
|
298
|
+
const stagedPaths = stagedNames.stdout.split('\0').map((path) => path.trim()).filter(Boolean);
|
|
299
|
+
const outsideScope = stagedPaths.filter((path) => !allowed.has(path));
|
|
300
|
+
if (outsideScope.length > 0) {
|
|
301
|
+
return {
|
|
302
|
+
repoId: repository.repoId,
|
|
303
|
+
checkoutId: repository.activeCheckoutId,
|
|
304
|
+
before,
|
|
305
|
+
stage,
|
|
306
|
+
after: repositoryGitStatus(repository),
|
|
307
|
+
committed: false,
|
|
308
|
+
error: { code: 'GIT_MERGE_STAGED_SCOPE_MISMATCH', message: `Resolved merge index contains staged path(s) outside the requested commit scope: ${outsideScope.join(', ')}` },
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
const commitArgs = ['commit', '-m', message, ...(input.allowEmpty === true ? ['--allow-empty'] : []), ...(!mergeInProgress && paths.length > 0 ? ['--only', '--', ...paths] : [])];
|
|
278
314
|
const commit = executeRepositoryGitCommand(controllerHome, repository, { args: commitArgs, authorization: 'explicit_user_request', ...input });
|
|
279
315
|
const ok = commit.status === 'executed' && commit.ok === true;
|
|
280
316
|
return {
|
|
@@ -289,12 +325,30 @@ export function repositoryGitCommit(controllerHome: string, repository: Reposito
|
|
|
289
325
|
};
|
|
290
326
|
}
|
|
291
327
|
|
|
292
|
-
export function repositoryGitFinishWorkflow(controllerHome: string, repository: RepositoryRecord, input: { targetBranch?: unknown; featureBranch?: unknown; deleteBranch?: unknown; noFf?: unknown; authorizationDecision?: AuthorizationDecision; sessionId?: string; principalId?: string; workId?: string; goalId?: string }): RepositoryGitFinishResult {
|
|
328
|
+
export function repositoryGitFinishWorkflow(controllerHome: string, repository: RepositoryRecord, input: { targetBranch?: unknown; featureBranch?: unknown; deleteBranch?: unknown; noFf?: unknown; preserveDirtyTargetPaths?: unknown; authorizationDecision?: AuthorizationDecision; sessionId?: string; principalId?: string; workId?: string; goalId?: string }): RepositoryGitFinishResult {
|
|
293
329
|
const before = repositoryGitStatus(repository);
|
|
294
330
|
const featureBranch = assertSafeBranchName(input.featureBranch ?? before.branch);
|
|
295
331
|
const targetBranch = assertSafeBranchName(input.targetBranch ?? repository.defaultBranch ?? 'main');
|
|
296
332
|
const steps: RepositoryGitFinishResult['steps'] = [];
|
|
297
333
|
const alreadyOnTarget = before.branch === targetBranch;
|
|
334
|
+
const requestedPreservedDirtyPaths = normalizePaths(input.preserveDirtyTargetPaths);
|
|
335
|
+
const currentDirtyPaths = [...new Set([...before.staged, ...before.unstaged, ...before.untracked])].sort();
|
|
336
|
+
const preserveDirtyTargetInPlace = alreadyOnTarget && requestedPreservedDirtyPaths.length > 0;
|
|
337
|
+
if (preserveDirtyTargetInPlace) {
|
|
338
|
+
const requested = new Set(requestedPreservedDirtyPaths);
|
|
339
|
+
if (requested.size !== currentDirtyPaths.length || currentDirtyPaths.some((path) => !requested.has(path))) {
|
|
340
|
+
return { repoId: repository.repoId, checkoutId: repository.activeCheckoutId, featureBranch, targetBranch, before, steps, after: before, completed: false, error: { code: 'GIT_DIRTY_TARGET_OWNERSHIP_MISMATCH', message: 'The exact current dirty target paths no longer match the Work-owned preservation set; retry ownership inspection before integration.' } };
|
|
341
|
+
}
|
|
342
|
+
const candidateDiff = runGit(repository, ['diff', '--name-only', `${targetBranch}..${featureBranch}`], 128 * 1024);
|
|
343
|
+
if (!candidateDiff.ok) {
|
|
344
|
+
return { repoId: repository.repoId, checkoutId: repository.activeCheckoutId, featureBranch, targetBranch, before, steps, after: before, completed: false, error: { code: 'GIT_DIRTY_TARGET_PREFLIGHT_FAILED', message: candidateDiff.stderr || 'Unable to inspect candidate paths before dirty-target integration.' } };
|
|
345
|
+
}
|
|
346
|
+
const candidatePaths = new Set(candidateDiff.stdout.split(/\r?\n/).map((path) => path.trim()).filter(Boolean));
|
|
347
|
+
const overlapping = currentDirtyPaths.filter((path) => candidatePaths.has(path));
|
|
348
|
+
if (overlapping.length > 0) {
|
|
349
|
+
return { repoId: repository.repoId, checkoutId: repository.activeCheckoutId, featureBranch, targetBranch, before, steps, after: before, completed: false, error: { code: 'GIT_DIRTY_TARGET_PATH_CONFLICT', message: `Work-owned dirty target path(s) overlap the integration candidate: ${overlapping.join(', ')}` } };
|
|
350
|
+
}
|
|
351
|
+
}
|
|
298
352
|
if (!before.clean && !alreadyOnTarget) {
|
|
299
353
|
return { repoId: repository.repoId, checkoutId: repository.activeCheckoutId, featureBranch, targetBranch, before, steps, after: before, completed: false, error: { code: 'GIT_WORKTREE_NOT_CLEAN', message: 'Commit or revert changes before switching branches to finish a workflow.' } };
|
|
300
354
|
}
|
|
@@ -306,7 +360,7 @@ export function repositoryGitFinishWorkflow(controllerHome: string, repository:
|
|
|
306
360
|
steps.push({ name: 'switch_target', execution: switchTarget });
|
|
307
361
|
if (switchTarget.status !== 'executed' || switchTarget.ok !== true) return { repoId: repository.repoId, checkoutId: repository.activeCheckoutId, featureBranch, targetBranch, before, steps, after: repositoryGitStatus(repository), completed: false, error: { code: 'GIT_SWITCH_TARGET_FAILED', message: switchTarget.stderr || 'git switch failed' } };
|
|
308
362
|
}
|
|
309
|
-
const protectTrackedChanges = alreadyOnTarget && (before.staged.length > 0 || before.unstaged.length > 0);
|
|
363
|
+
const protectTrackedChanges = alreadyOnTarget && !preserveDirtyTargetInPlace && (before.staged.length > 0 || before.unstaged.length > 0);
|
|
310
364
|
const finishAuthorization = 'explicit_user_request' as const;
|
|
311
365
|
let stashRef: string | null = null;
|
|
312
366
|
if (protectTrackedChanges) {
|
|
@@ -733,7 +733,9 @@ export function searchRepositoryMany(
|
|
|
733
733
|
policyDeniedFiles += 1;
|
|
734
734
|
continue;
|
|
735
735
|
}
|
|
736
|
-
const
|
|
736
|
+
const info = statSync(decision.absolutePath);
|
|
737
|
+
if (!info.isFile()) continue;
|
|
738
|
+
const size = info.size;
|
|
737
739
|
if (size > policy.maxFileBytes) {
|
|
738
740
|
skippedLargeFiles += 1;
|
|
739
741
|
continue;
|
|
@@ -780,6 +782,7 @@ type AsyncSearchCandidate =
|
|
|
780
782
|
| { path: string; kind: 'denied' }
|
|
781
783
|
| { path: string; kind: 'large' }
|
|
782
784
|
| { path: string; kind: 'binary' }
|
|
785
|
+
| { path: string; kind: 'nonfile' }
|
|
783
786
|
| { path: string; kind: 'source'; bytes: Buffer }
|
|
784
787
|
| { path: string; kind: 'error'; error: unknown };
|
|
785
788
|
|
|
@@ -792,7 +795,9 @@ async function readSearchCandidateAsync(
|
|
|
792
795
|
try {
|
|
793
796
|
const decision = resolveMcpPath(repoRoot, path, policy, 'read', canonicalRepoRoot);
|
|
794
797
|
if (!decision.ok || !decision.absolutePath) return { path, kind: 'denied' };
|
|
795
|
-
const
|
|
798
|
+
const info = await statAsync(decision.absolutePath);
|
|
799
|
+
if (!info.isFile()) return { path, kind: 'nonfile' };
|
|
800
|
+
const size = info.size;
|
|
796
801
|
if (size > policy.maxFileBytes) return { path, kind: 'large' };
|
|
797
802
|
const bytes = await readFileAsync(decision.absolutePath);
|
|
798
803
|
if (binary(bytes)) return { path, kind: 'binary' };
|
|
@@ -872,6 +877,7 @@ export async function searchRepositoryManyAsync(
|
|
|
872
877
|
skippedBinaryFiles += 1;
|
|
873
878
|
continue;
|
|
874
879
|
}
|
|
880
|
+
if (candidate.kind === 'nonfile') continue;
|
|
875
881
|
if (candidate.kind === 'error') throw candidate.error;
|
|
876
882
|
scannedFiles += 1;
|
|
877
883
|
matchSearchManySource(matchState, candidate.path, candidate.bytes);
|
|
@@ -950,7 +956,9 @@ export function searchRepository(repoRoot: string, policy: McpPolicy, opts: Sear
|
|
|
950
956
|
policyDeniedFiles += 1;
|
|
951
957
|
continue;
|
|
952
958
|
}
|
|
953
|
-
const
|
|
959
|
+
const info = statSync(decision.absolutePath);
|
|
960
|
+
if (!info.isFile()) continue;
|
|
961
|
+
const size = info.size;
|
|
954
962
|
if (size > policy.maxFileBytes) {
|
|
955
963
|
skippedLargeFiles += 1;
|
|
956
964
|
continue;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export type SemanticNavigationKind = 'definition' | 'references' | 'implementations';
|
|
2
|
+
|
|
3
|
+
export interface SemanticNavigationRequest {
|
|
4
|
+
navigation: SemanticNavigationKind;
|
|
5
|
+
path: string;
|
|
6
|
+
line: number;
|
|
7
|
+
column: number;
|
|
8
|
+
tsconfigPath?: string;
|
|
9
|
+
language?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SemanticNavigationLocation {
|
|
13
|
+
path: string;
|
|
14
|
+
line: number;
|
|
15
|
+
column: number;
|
|
16
|
+
name?: string;
|
|
17
|
+
kind?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SemanticNavigationResult {
|
|
21
|
+
providerId: string;
|
|
22
|
+
providerIdentity?: string;
|
|
23
|
+
language: string;
|
|
24
|
+
navigation: SemanticNavigationKind;
|
|
25
|
+
target: { path: string; line: number; column: number };
|
|
26
|
+
locations: SemanticNavigationLocation[];
|
|
27
|
+
policyDeniedReads?: number;
|
|
28
|
+
details?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type SemanticNavigationOutcome =
|
|
32
|
+
| { ok: true; result: SemanticNavigationResult }
|
|
33
|
+
| { ok: false; code: string; message: string };
|
|
34
|
+
|
|
35
|
+
export interface SemanticNavigationAccess {
|
|
36
|
+
cacheScope: string;
|
|
37
|
+
sourceIdentity?: string;
|
|
38
|
+
profile: string;
|
|
39
|
+
allowRepositoryPath(relativePath: string): boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SemanticNavigationProvider {
|
|
43
|
+
id: string;
|
|
44
|
+
languages: readonly string[];
|
|
45
|
+
supports(request: SemanticNavigationRequest): boolean;
|
|
46
|
+
navigate(
|
|
47
|
+
repoRoot: string,
|
|
48
|
+
requests: SemanticNavigationRequest[],
|
|
49
|
+
access: SemanticNavigationAccess,
|
|
50
|
+
): Promise<SemanticNavigationOutcome[]>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface IndexedSemanticNavigationRequest {
|
|
54
|
+
index: number;
|
|
55
|
+
request: SemanticNavigationRequest;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface IndexedSemanticNavigationOutcome {
|
|
59
|
+
index: number;
|
|
60
|
+
outcome: SemanticNavigationOutcome;
|
|
61
|
+
}
|
|
@@ -3,67 +3,15 @@ import { navigateTypeScriptSymbol } from './typescript-navigation';
|
|
|
3
3
|
import { navigateSwiftSymbols } from './swift-navigation';
|
|
4
4
|
import { GenericLspSemanticProvider, type GenericLspProviderDescriptor } from './generic-lsp-provider';
|
|
5
5
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface SemanticNavigationLocation {
|
|
18
|
-
path: string;
|
|
19
|
-
line: number;
|
|
20
|
-
column: number;
|
|
21
|
-
name?: string;
|
|
22
|
-
kind?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface SemanticNavigationResult {
|
|
26
|
-
providerId: string;
|
|
27
|
-
providerIdentity?: string;
|
|
28
|
-
language: string;
|
|
29
|
-
navigation: SemanticNavigationKind;
|
|
30
|
-
target: { path: string; line: number; column: number };
|
|
31
|
-
locations: SemanticNavigationLocation[];
|
|
32
|
-
policyDeniedReads?: number;
|
|
33
|
-
details?: Record<string, unknown>;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export type SemanticNavigationOutcome =
|
|
37
|
-
| { ok: true; result: SemanticNavigationResult }
|
|
38
|
-
| { ok: false; code: string; message: string };
|
|
39
|
-
|
|
40
|
-
export interface SemanticNavigationAccess {
|
|
41
|
-
cacheScope: string;
|
|
42
|
-
sourceIdentity?: string;
|
|
43
|
-
profile: string;
|
|
44
|
-
allowRepositoryPath(relativePath: string): boolean;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface SemanticNavigationProvider {
|
|
48
|
-
id: string;
|
|
49
|
-
languages: readonly string[];
|
|
50
|
-
supports(request: SemanticNavigationRequest): boolean;
|
|
51
|
-
navigate(
|
|
52
|
-
repoRoot: string,
|
|
53
|
-
requests: SemanticNavigationRequest[],
|
|
54
|
-
access: SemanticNavigationAccess,
|
|
55
|
-
): Promise<SemanticNavigationOutcome[]>;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export interface IndexedSemanticNavigationRequest {
|
|
59
|
-
index: number;
|
|
60
|
-
request: SemanticNavigationRequest;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface IndexedSemanticNavigationOutcome {
|
|
64
|
-
index: number;
|
|
65
|
-
outcome: SemanticNavigationOutcome;
|
|
66
|
-
}
|
|
6
|
+
export * from './semantic-navigation-contract';
|
|
7
|
+
import type {
|
|
8
|
+
IndexedSemanticNavigationOutcome,
|
|
9
|
+
IndexedSemanticNavigationRequest,
|
|
10
|
+
SemanticNavigationAccess,
|
|
11
|
+
SemanticNavigationOutcome,
|
|
12
|
+
SemanticNavigationProvider,
|
|
13
|
+
SemanticNavigationRequest,
|
|
14
|
+
} from './semantic-navigation-contract';
|
|
67
15
|
|
|
68
16
|
function extensionMatches(path: string, values: readonly string[]): boolean {
|
|
69
17
|
return values.includes(extname(path).toLowerCase());
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
2
|
import { getEditSession, listEditSessions, type EditSession } from '../../../cli/editing/edit-session';
|
|
3
|
-
import { globMatches } from '../../../cli/mcp/paths';
|
|
4
3
|
import { runProcess } from '../../../effects/process-runner';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { getWorkContract, updateWorkContract } from '../facade/work-contract-store';
|
|
5
|
+
import { completeWorkWithReceipt } from './work-completion-authority';
|
|
7
6
|
import { isDirectEditWorkCompletionReceipt, isTerminalWorkContractStatus, type DirectEditWorkCompletionReceipt, type WorkReconciliationRecord } from '../facade/types';
|
|
8
7
|
import { historicalVerificationEvidenceAtRevision } from './verification-evidence';
|
|
9
8
|
import { readWorkHandle } from './work-handle-store';
|
|
9
|
+
import { assertWorkPathsWithinScope, findWorkPathScopeViolation } from './work-path-scope';
|
|
10
10
|
|
|
11
11
|
export interface DirectEditWorkCompletionReconciliation {
|
|
12
12
|
completedWorkIds: string[];
|
|
@@ -102,6 +102,15 @@ export function reconcileFinalizedDirectEditWorksAfterCommit(input: {
|
|
|
102
102
|
skipped.push({ sessionId: session.sessionId, workId: session.workId, reason: 'configured_checks_not_passed' });
|
|
103
103
|
continue;
|
|
104
104
|
}
|
|
105
|
+
const scopeViolation = findWorkPathScopeViolation(work, paths);
|
|
106
|
+
if (scopeViolation) {
|
|
107
|
+
skipped.push({
|
|
108
|
+
sessionId: session.sessionId,
|
|
109
|
+
workId: session.workId,
|
|
110
|
+
reason: `${scopeViolation.kind === 'forbidden' ? 'work_scope_forbidden' : 'work_scope_out_of_scope'}:${scopeViolation.path}`,
|
|
111
|
+
});
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
105
114
|
const status = git(input.repoRoot, ['status', '--porcelain=v1', '--', ...paths]);
|
|
106
115
|
if (!status.ok || status.stdout.trim()) {
|
|
107
116
|
skipped.push({ sessionId: session.sessionId, workId: session.workId, reason: 'owned_paths_not_clean' });
|
|
@@ -141,7 +150,7 @@ export function reconcileFinalizedDirectEditWorksAfterCommit(input: {
|
|
|
141
150
|
verifiedAt: session.verifiedAt ?? session.finalizedAt ?? recordedAt,
|
|
142
151
|
recordedAt,
|
|
143
152
|
};
|
|
144
|
-
|
|
153
|
+
completeWorkWithReceipt(
|
|
145
154
|
{ controllerHome: input.controllerHome, repoId: input.repoId },
|
|
146
155
|
session.workId,
|
|
147
156
|
receipt,
|
|
@@ -248,10 +257,10 @@ export function acceptReviewedDirectEditWorkReconciliation(input: ReviewedDirect
|
|
|
248
257
|
if (comparedPaths.length !== actualPaths.length || comparedPaths.some((path, index) => path !== actualPaths[index])) {
|
|
249
258
|
throw new Error('DIRECT_EDIT_WORK_RECONCILIATION_PATH_COMPARISON_MISMATCH');
|
|
250
259
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
260
|
+
assertWorkPathsWithinScope(work, comparedPaths, {
|
|
261
|
+
forbidden: 'DIRECT_EDIT_WORK_RECONCILIATION_FORBIDDEN_PATH',
|
|
262
|
+
outOfScope: 'DIRECT_EDIT_WORK_RECONCILIATION_PATH_OUT_OF_SCOPE',
|
|
263
|
+
});
|
|
255
264
|
const dirty = git(input.repoRoot, ['status', '--porcelain=v1', '--', ...comparedPaths]);
|
|
256
265
|
if (!dirty.ok || dirty.stdout.trim()) throw new Error('DIRECT_EDIT_WORK_RECONCILIATION_OWNED_PATHS_DIRTY');
|
|
257
266
|
|
|
@@ -318,28 +327,12 @@ export function acceptReviewedDirectEditWorkReconciliation(input: ReviewedDirect
|
|
|
318
327
|
verifiedAt,
|
|
319
328
|
recordedAt,
|
|
320
329
|
};
|
|
321
|
-
|
|
330
|
+
completeWorkWithReceipt(
|
|
322
331
|
{ controllerHome: input.controllerHome, repoId: input.repoId },
|
|
323
332
|
input.workId,
|
|
324
333
|
receipt,
|
|
325
334
|
'completed_changed',
|
|
326
335
|
'repository_change',
|
|
327
336
|
);
|
|
328
|
-
if (recorded.requirementId) {
|
|
329
|
-
try {
|
|
330
|
-
completeRequirementFromWork({ controllerHome: input.controllerHome }, { requirementId: recorded.requirementId, work: recorded });
|
|
331
|
-
} catch (error) {
|
|
332
|
-
try {
|
|
333
|
-
appendWorkEvidence({ controllerHome: input.controllerHome, repoId: input.repoId }, input.workId, {
|
|
334
|
-
title: 'requirement completion projection pending',
|
|
335
|
-
summary: `Work completion remains authoritative; Requirement projection could not be applied: ${error instanceof Error ? error.message : String(error)}`.slice(0, 2_000),
|
|
336
|
-
detailLevel: 'summary',
|
|
337
|
-
});
|
|
338
|
-
} catch {
|
|
339
|
-
// Reconciliation already recorded the terminal receipt. A diagnostic
|
|
340
|
-
// write failure cannot make that completed Work non-terminal again.
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
337
|
return { workId: input.workId, reconciliation, receipt };
|
|
345
338
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { readExecutionSession } from './session-store';
|
|
2
|
+
import { controllerSessionPrincipalId, getControllerSession, listControllerSessions } from '../facade/controller-session-store';
|
|
3
|
+
import { getWorkContract } from '../facade/work-contract-store';
|
|
4
|
+
import { isTerminalWorkContractStatus } from '../facade/types';
|
|
5
|
+
|
|
6
|
+
export interface RepositoryWorkAttributionCaller {
|
|
7
|
+
sessionId?: string;
|
|
8
|
+
principalId?: string;
|
|
9
|
+
controllerInstanceId?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface RepositoryWorkAttributionTarget {
|
|
13
|
+
repoId: string;
|
|
14
|
+
activeCheckoutId: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function resolveExplicitClaimedRepositoryWork(
|
|
18
|
+
controllerHome: string,
|
|
19
|
+
target: RepositoryWorkAttributionTarget,
|
|
20
|
+
caller: RepositoryWorkAttributionCaller | undefined,
|
|
21
|
+
workId: string,
|
|
22
|
+
) {
|
|
23
|
+
if (!caller?.principalId?.trim()) return undefined;
|
|
24
|
+
const work = getWorkContract({ controllerHome, repoId: target.repoId }, workId);
|
|
25
|
+
if (!work || isTerminalWorkContractStatus(work.status)) throw new Error(`WORK_ATTRIBUTION_INVALID: ${workId}`);
|
|
26
|
+
const owner = getControllerSession({ controllerHome, repoId: target.repoId }, workId);
|
|
27
|
+
if (!owner) throw new Error(`WORK_CONTROLLER_CLAIM_REQUIRED: ${workId}`);
|
|
28
|
+
if (controllerSessionPrincipalId(owner) !== caller.principalId.trim()) {
|
|
29
|
+
throw new Error(`WORK_CONTROLLER_OWNERSHIP_MISMATCH: ${workId}`);
|
|
30
|
+
}
|
|
31
|
+
if (work.checkoutId && work.checkoutId !== target.activeCheckoutId) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`WORK_CHECKOUT_MISMATCH: work=${workId}; resolved_checkout=${target.activeCheckoutId}; expected_work_checkout=${work.checkoutId}; retry with checkout_id=${work.checkoutId} and the same work_id`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return work;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function assertNoBoundExecutionSessionMutation(
|
|
40
|
+
controllerHome: string,
|
|
41
|
+
target: RepositoryWorkAttributionTarget,
|
|
42
|
+
caller?: RepositoryWorkAttributionCaller,
|
|
43
|
+
): void {
|
|
44
|
+
if (!caller?.principalId?.trim() || !caller.sessionId?.trim()) return;
|
|
45
|
+
const executionSession = readExecutionSession(controllerHome, {
|
|
46
|
+
sessionId: caller.sessionId,
|
|
47
|
+
principalId: caller.principalId,
|
|
48
|
+
controllerInstanceId: caller.controllerInstanceId,
|
|
49
|
+
});
|
|
50
|
+
const workId = executionSession?.activeWorkId?.trim();
|
|
51
|
+
if (!workId || executionSession?.activeRepositoryId !== target.repoId) return;
|
|
52
|
+
const work = getWorkContract({ controllerHome, repoId: target.repoId }, workId);
|
|
53
|
+
if (!work) throw new Error(`WORK_ATTRIBUTION_INVALID: ${workId}`);
|
|
54
|
+
if (isTerminalWorkContractStatus(work.status)) {
|
|
55
|
+
throw new Error(`WORK_ATTRIBUTION_TERMINAL: ${work.workId}:${work.status}`);
|
|
56
|
+
}
|
|
57
|
+
throw new Error(`WORK_ATTRIBUTION_REQUIRED: ${work.workId}; active execution session mutations must pass work_id explicitly`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function resolveClaimedRepositoryWorkId(
|
|
61
|
+
controllerHome: string,
|
|
62
|
+
target: RepositoryWorkAttributionTarget,
|
|
63
|
+
caller?: RepositoryWorkAttributionCaller,
|
|
64
|
+
explicitWorkId?: unknown,
|
|
65
|
+
): string | undefined {
|
|
66
|
+
const requestedWorkId = typeof explicitWorkId === 'string' ? explicitWorkId.trim() : '';
|
|
67
|
+
if (requestedWorkId) {
|
|
68
|
+
const work = resolveExplicitClaimedRepositoryWork(controllerHome, target, caller, requestedWorkId);
|
|
69
|
+
return work?.workId;
|
|
70
|
+
}
|
|
71
|
+
if (!caller?.principalId?.trim()) return undefined;
|
|
72
|
+
if (caller.sessionId?.trim()) {
|
|
73
|
+
const executionSession = readExecutionSession(controllerHome, {
|
|
74
|
+
sessionId: caller.sessionId,
|
|
75
|
+
principalId: caller.principalId,
|
|
76
|
+
controllerInstanceId: caller.controllerInstanceId,
|
|
77
|
+
});
|
|
78
|
+
const workId = executionSession?.activeWorkId?.trim();
|
|
79
|
+
if (workId
|
|
80
|
+
&& executionSession?.activeRepositoryId === target.repoId
|
|
81
|
+
&& (!executionSession.activeCheckoutId || executionSession.activeCheckoutId === target.activeCheckoutId)) {
|
|
82
|
+
const work = getWorkContract({ controllerHome, repoId: target.repoId }, workId);
|
|
83
|
+
if (work && isTerminalWorkContractStatus(work.status)) {
|
|
84
|
+
throw new Error(`WORK_ATTRIBUTION_TERMINAL: ${work.workId}:${work.status}`);
|
|
85
|
+
}
|
|
86
|
+
const owner = getControllerSession({ controllerHome, repoId: target.repoId }, workId);
|
|
87
|
+
if (work && owner?.sessionId === caller.sessionId && controllerSessionPrincipalId(owner) === caller.principalId.trim()) {
|
|
88
|
+
return workId;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const principal = caller.principalId.trim();
|
|
93
|
+
const candidates = listControllerSessions({ controllerHome, repoId: target.repoId })
|
|
94
|
+
.filter((owner) => controllerSessionPrincipalId(owner) === principal)
|
|
95
|
+
.map((owner) => ({ owner, work: getWorkContract({ controllerHome, repoId: target.repoId }, owner.workId) }))
|
|
96
|
+
.filter((entry): entry is { owner: ReturnType<typeof listControllerSessions>[number]; work: NonNullable<ReturnType<typeof getWorkContract>> } => Boolean(
|
|
97
|
+
entry.work
|
|
98
|
+
&& !isTerminalWorkContractStatus(entry.work.status)
|
|
99
|
+
&& (!entry.work.checkoutId || entry.work.checkoutId === target.activeCheckoutId),
|
|
100
|
+
));
|
|
101
|
+
if (candidates.length === 1) return candidates[0].work.workId;
|
|
102
|
+
if (candidates.length > 1) {
|
|
103
|
+
throw new Error(`WORK_ATTRIBUTION_AMBIGUOUS: principal ${principal} owns ${candidates.length} active Works on checkout ${target.activeCheckoutId}`);
|
|
104
|
+
}
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|