@open-agent-toolkit/cli 0.3.16 → 0.3.18
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/assets/agents/oat-phase-implementer.md +26 -6
- package/assets/agents/oat-reviewer.md +51 -1
- package/assets/bundle-metadata.json +1 -1
- package/assets/docs/contributing/documentation.md +4 -0
- package/assets/docs/contributing/skills.md +3 -1
- package/assets/docs/getting-started/bootstrap.md +1 -1
- package/assets/docs/getting-started/index.md +0 -4
- package/assets/docs/getting-started/tool-packs.md +0 -7
- package/assets/docs/reference/cli-reference.md +42 -1
- package/assets/docs/reference/config-and-local-state.md +11 -2
- package/assets/docs/reference/project-artifacts.md +43 -8
- package/assets/docs/skills/repo-improve.md +9 -7
- package/assets/docs/workflows/advanced/autonomy.md +40 -0
- package/assets/docs/workflows/backlog-and-planning/backlog-lifecycle.md +25 -24
- package/assets/docs/workflows/backlog-and-planning/remote-project-management.md +0 -7
- package/assets/docs/workflows/ideas/lifecycle.md +2 -7
- package/assets/docs/workflows/projects/execution/project-log.md +22 -0
- package/assets/docs/workflows/projects/reviews/review-flavors.md +8 -0
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-agent-instructions-apply/SKILL.md +14 -4
- package/assets/skills/oat-brainstorm/SKILL.md +17 -13
- package/assets/skills/oat-docs-apply/SKILL.md +13 -3
- package/assets/skills/oat-doctor/SKILL.md +11 -1
- package/assets/skills/oat-explainer-kit/SKILL.md +1 -1
- package/assets/skills/oat-explainer-kit/references/lifecycle-contract.md +6 -3
- package/assets/skills/oat-pjm-review-backlog/SKILL.md +2 -2
- package/assets/skills/oat-pjm-update-repo-reference/SKILL.md +28 -3
- package/assets/skills/oat-project-autonomous/SKILL.md +60 -5
- package/assets/skills/oat-project-autonomous/references/gate-inventory.md +44 -6
- package/assets/skills/oat-project-capture/SKILL.md +7 -3
- package/assets/skills/oat-project-complete/SKILL.md +39 -34
- package/assets/skills/oat-project-complete/scripts/execute-synced-archive-entry.mjs +106 -28
- package/assets/skills/oat-project-complete/scripts/parse-synced-archive-resume-fields.mjs +30 -3
- package/assets/skills/oat-project-design/SKILL.md +12 -9
- package/assets/skills/oat-project-discover/SKILL.md +8 -4
- package/assets/skills/oat-project-document/SKILL.md +14 -5
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +44 -6
- package/assets/skills/oat-project-implement/SKILL.md +1 -1
- package/assets/skills/oat-project-implement/references/completion-and-closeout.md +6 -6
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +44 -6
- package/assets/skills/oat-project-implement/references/phase-execution.md +20 -6
- package/assets/skills/oat-project-import-plan/SKILL.md +20 -6
- package/assets/skills/oat-project-lite/SKILL.md +7 -5
- package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +44 -6
- package/assets/skills/oat-project-new/SKILL.md +6 -2
- package/assets/skills/oat-project-plan/SKILL.md +14 -6
- package/assets/skills/oat-project-pr-final/SKILL.md +1 -1
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +44 -6
- package/assets/skills/oat-project-progress/SKILL.md +13 -1
- package/assets/skills/oat-project-promote-spec-driven/SKILL.md +7 -3
- package/assets/skills/oat-project-quick-start/SKILL.md +17 -12
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +44 -6
- package/assets/skills/oat-project-reconcile/SKILL.md +8 -4
- package/assets/skills/oat-project-retro/SKILL.md +1 -1
- package/assets/skills/oat-project-retro/references/apply-procedure.md +6 -6
- package/assets/skills/oat-project-retro-file/SKILL.md +7 -3
- package/assets/skills/oat-project-review-provide/SKILL.md +23 -4
- package/assets/skills/oat-project-review-provide-remote/SKILL.md +19 -1
- package/assets/skills/oat-project-review-receive/SKILL.md +37 -12
- package/assets/skills/oat-project-review-receive-remote/SKILL.md +7 -3
- package/assets/skills/oat-project-revise/SKILL.md +13 -5
- package/assets/skills/oat-project-spec/SKILL.md +8 -4
- package/assets/skills/oat-project-summary/SKILL.md +11 -7
- package/assets/skills/oat-repo-knowledge-index/SKILL.md +54 -20
- package/assets/skills/oat-repo-knowledge-index/scripts/refresh-owned.mjs +118 -0
- package/assets/skills/oat-review-provide/SKILL.md +7 -3
- package/assets/skills/oat-review-receive-remote/SKILL.md +14 -4
- package/assets/skills/oat-wave-execute/SKILL.md +24 -11
- package/assets/skills/oat-wave-execute/assets/wrapper-plan-template.md +16 -2
- package/assets/skills/oat-worktree-bootstrap-auto/SKILL.md +45 -17
- package/assets/templates/pjm-agents.md +38 -8
- package/assets/templates/repo-agents.md +5 -0
- package/assets/templates/repo-readme.md +5 -0
- package/dist/commands/backlog/archive.d.ts +3 -0
- package/dist/commands/backlog/archive.d.ts.map +1 -1
- package/dist/commands/backlog/archive.js +63 -26
- package/dist/commands/backlog/index.d.ts.map +1 -1
- package/dist/commands/backlog/index.js +3 -0
- package/dist/commands/backlog/rewrite-references.d.ts +5 -0
- package/dist/commands/backlog/rewrite-references.d.ts.map +1 -1
- package/dist/commands/backlog/rewrite-references.js +39 -1
- package/dist/commands/init/index.d.ts.map +1 -1
- package/dist/commands/init/index.js +1 -0
- package/dist/commands/init/tools/shared/skill-script-references.d.ts.map +1 -1
- package/dist/commands/init/tools/shared/skill-script-references.js +8 -1
- package/dist/commands/internal/commit-paths.d.ts +3 -0
- package/dist/commands/internal/commit-paths.d.ts.map +1 -0
- package/dist/commands/internal/commit-paths.js +31 -0
- package/dist/commands/internal/index.d.ts.map +1 -1
- package/dist/commands/internal/index.js +2 -0
- package/dist/commands/pjm/init.d.ts.map +1 -1
- package/dist/commands/pjm/init.js +37 -8
- package/dist/commands/project/archive/archive-utils.d.ts +6 -3
- package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
- package/dist/commands/project/archive/archive-utils.js +510 -104
- package/dist/commands/project/archive/push-runner.d.ts.map +1 -1
- package/dist/commands/project/archive/push-runner.js +1 -0
- package/dist/commands/project/log/append.d.ts +2 -3
- package/dist/commands/project/log/append.d.ts.map +1 -1
- package/dist/commands/project/log/append.js +125 -111
- package/dist/commands/project/migrate/index.d.ts +1 -2
- package/dist/commands/project/migrate/index.d.ts.map +1 -1
- package/dist/commands/project/migrate/index.js +8 -2
- package/dist/commands/project/new/scaffold.d.ts.map +1 -1
- package/dist/commands/project/new/scaffold.js +4 -4
- package/dist/commands/project/promote/promote.d.ts +8 -0
- package/dist/commands/project/promote/promote.d.ts.map +1 -1
- package/dist/commands/project/promote/promote.js +37 -6
- package/dist/commands/project/status.d.ts.map +1 -1
- package/dist/commands/project/status.js +3 -0
- package/dist/commands/project/sync/ref-sync.d.ts +12 -1
- package/dist/commands/project/sync/ref-sync.d.ts.map +1 -1
- package/dist/commands/project/sync/ref-sync.js +347 -23
- package/dist/commands/shared/agents-md-lock.d.ts +3 -0
- package/dist/commands/shared/agents-md-lock.d.ts.map +1 -0
- package/dist/commands/shared/agents-md-lock.js +130 -0
- package/dist/commands/shared/agents-md.d.ts.map +1 -1
- package/dist/commands/shared/agents-md.js +26 -8
- package/dist/commands/shared/exact-path-commit.d.ts +32 -0
- package/dist/commands/shared/exact-path-commit.d.ts.map +1 -0
- package/dist/commands/shared/exact-path-commit.js +691 -0
- package/dist/validation/skills.d.ts.map +1 -1
- package/dist/validation/skills.js +138 -11
- package/package.json +3 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/commands/project/status.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAEL,KAAK,YAAY,EAClB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,UAAU,yBAAyB;IACjC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC7E,eAAe,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;CACjE;
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/commands/project/status.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAEL,KAAK,YAAY,EAClB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,UAAU,yBAAyB;IACjC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC7E,eAAe,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;CACjE;AAqND,wBAAgB,0BAA0B,CACxC,SAAS,GAAE,OAAO,CAAC,yBAAyB,CAAM,GACjD,OAAO,CA0BT"}
|
|
@@ -21,6 +21,9 @@ function formatProjectStatusLines(project) {
|
|
|
21
21
|
`Phase: ${project.phase} (${project.phaseStatus})`,
|
|
22
22
|
`Progress: ${project.progress.completed}/${project.progress.total}`,
|
|
23
23
|
`Current task: ${project.progress.currentTaskId ?? 'none'}`,
|
|
24
|
+
...project.blockers.map((blocker) => typeof blocker === 'string'
|
|
25
|
+
? `Blocker: ${blocker}`
|
|
26
|
+
: `Blocker: ${blocker.task_id}: ${blocker.reason} (since ${blocker.since})`),
|
|
24
27
|
`Recommendation: ${project.recommendation.skill}`,
|
|
25
28
|
`Reason: ${project.recommendation.reason}`,
|
|
26
29
|
];
|
|
@@ -110,7 +110,17 @@ export declare function continueSynced(target: SyncTarget, git: GitRunner, optio
|
|
|
110
110
|
now?: Date;
|
|
111
111
|
}): Promise<PullResult>;
|
|
112
112
|
export declare function abortSynced(target: SyncTarget, git: GitRunner): Promise<void>;
|
|
113
|
-
|
|
113
|
+
interface RecordCommitSettlement {
|
|
114
|
+
commit: string;
|
|
115
|
+
marker: {
|
|
116
|
+
identity: string;
|
|
117
|
+
bytes: string;
|
|
118
|
+
ino: number;
|
|
119
|
+
dev: number;
|
|
120
|
+
};
|
|
121
|
+
finalize: () => Promise<void>;
|
|
122
|
+
}
|
|
123
|
+
export declare function commitRecordChange(repoRoot: string, pathspecs: string[], message: string, git: GitRunner, options: AllowlistedPathspecOptions, settlement?: RecordCommitSettlement): Promise<{
|
|
114
124
|
sha: string;
|
|
115
125
|
} | null>;
|
|
116
126
|
export declare function preflightSyncedCheckout(target: SyncTarget, git: GitRunner): Promise<CheckoutPreflight>;
|
|
@@ -132,4 +142,5 @@ export declare function migrateSharedToSynced(target: SyncTarget, git: GitRunner
|
|
|
132
142
|
export declare function rollbackCreatedSyncedProject(target: SyncTarget, git: GitRunner): Promise<void>;
|
|
133
143
|
export declare function assertNestedWorktree(target: SyncTarget, git: GitRunner): Promise<void>;
|
|
134
144
|
export declare function assertAllowlistedPathspecs(repoRoot: string, pathspecs: string[], options: AllowlistedPathspecOptions): void;
|
|
145
|
+
export {};
|
|
135
146
|
//# sourceMappingURL=ref-sync.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ref-sync.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/sync/ref-sync.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ref-sync.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/sync/ref-sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { lstat, link, mkdir, mkdtemp, open, readFile, readdir, realpath, rm, stat, writeFile, } from 'node:fs/promises';
|
|
2
3
|
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
3
4
|
import { ensureScopedRootGitignore } from '../../init/gitignore.js';
|
|
5
|
+
import { commitExactPaths, } from '../../shared/exact-path-commit.js';
|
|
4
6
|
import { getFrontmatterBlock } from '../../shared/frontmatter.js';
|
|
5
7
|
import { canonicalizePath, completedSyncedRefName, isSyncedCheckout, resolveScopeRoot, SYNCED_REMOTE, syncedRefName, syncedRecordPath, } from '../../shared/project-scope.js';
|
|
6
8
|
import { readOatLocalConfig, writeOatLocalConfig } from '../../../config/oat-config.js';
|
|
@@ -8,7 +10,7 @@ import { CliError } from '../../../errors/cli-error.js';
|
|
|
8
10
|
import { copyDirectory } from '../../../fs/io.js';
|
|
9
11
|
import YAML from 'yaml';
|
|
10
12
|
import { isAtomicPushUnsupported, pushAtomicRefTransition, } from './git.js';
|
|
11
|
-
import { buildSyncedRecord, readSyncedRecord, writeSyncedRecord, } from './record.js';
|
|
13
|
+
import { buildSyncedRecord, readSyncedRecord, SyncedProjectRecordSchema, writeSyncedRecord, } from './record.js';
|
|
12
14
|
export function classifyRemoteRefLookup(result, remote, ref) {
|
|
13
15
|
if (result.code === 0)
|
|
14
16
|
return 'present';
|
|
@@ -593,22 +595,126 @@ export async function abortSynced(target, git) {
|
|
|
593
595
|
});
|
|
594
596
|
}
|
|
595
597
|
function normalizedPathspecs(repoRoot, pathspecs) {
|
|
596
|
-
return
|
|
598
|
+
return [
|
|
599
|
+
...new Set(pathspecs.map((pathspec) => repoRelativePath(repoRoot, pathspec))),
|
|
600
|
+
].sort();
|
|
597
601
|
}
|
|
598
|
-
|
|
602
|
+
class PendingRecordCommitError extends CliError {
|
|
603
|
+
result;
|
|
604
|
+
constructor(message, result) {
|
|
605
|
+
super(message, result.outcome === 'blocked' ? 1 : 2);
|
|
606
|
+
this.result = result;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
export async function commitRecordChange(repoRoot, pathspecs, message, git, options, settlement) {
|
|
599
610
|
assertAllowlistedPathspecs(repoRoot, pathspecs, options);
|
|
600
611
|
const normalized = normalizedPathspecs(repoRoot, pathspecs);
|
|
601
|
-
await git.run(['
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
612
|
+
await git.run(['rev-parse', '--is-inside-work-tree'], { cwd: repoRoot });
|
|
613
|
+
const gitDir = (await git.run(['rev-parse', '--absolute-git-dir'], { cwd: repoRoot })).stdout;
|
|
614
|
+
const pendingDir = join(gitDir, 'oat-record-commit-pending');
|
|
615
|
+
await mkdir(pendingDir, { recursive: true });
|
|
616
|
+
const operation = createHash('sha256')
|
|
617
|
+
.update(JSON.stringify([message, normalized]))
|
|
618
|
+
.digest('hex');
|
|
619
|
+
const pendingPath = join(pendingDir, `${operation}.json`);
|
|
620
|
+
for (let reservationAttempt = 0; reservationAttempt < 2; reservationAttempt++) {
|
|
621
|
+
if ((await readOptionalFile(pendingPath)) === null) {
|
|
622
|
+
if (settlement)
|
|
623
|
+
throw new CliError('The verified pending migration marker disappeared; preserve state for inspection.', 2);
|
|
624
|
+
const identity = `project-record:${operation}:${randomUUID()}`;
|
|
625
|
+
const temporary = await mkdtemp(join(pendingDir, 'reservation-'));
|
|
626
|
+
const complete = join(temporary, 'pending.json');
|
|
627
|
+
try {
|
|
628
|
+
await writeFile(complete, JSON.stringify({ identity, paths: normalized, message }));
|
|
629
|
+
try {
|
|
630
|
+
await link(complete, pendingPath);
|
|
631
|
+
}
|
|
632
|
+
catch (error) {
|
|
633
|
+
if (error.code !== 'EEXIST')
|
|
634
|
+
throw error;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
finally {
|
|
638
|
+
await rm(temporary, { recursive: true, force: true });
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
const file = await open(pendingPath, 'r');
|
|
642
|
+
const markerIdentity = await file.stat();
|
|
643
|
+
let markerBytes;
|
|
644
|
+
try {
|
|
645
|
+
markerBytes = await file.readFile('utf8');
|
|
646
|
+
}
|
|
647
|
+
finally {
|
|
648
|
+
await file.close();
|
|
649
|
+
}
|
|
650
|
+
const reservation = JSON.parse(markerBytes);
|
|
651
|
+
if (settlement &&
|
|
652
|
+
(reservation.identity !== settlement.marker.identity ||
|
|
653
|
+
markerBytes !== settlement.marker.bytes ||
|
|
654
|
+
markerIdentity.ino !== settlement.marker.ino ||
|
|
655
|
+
markerIdentity.dev !== settlement.marker.dev))
|
|
656
|
+
throw new CliError('The verified pending migration marker was replaced; replacement preserved for inspection.', 2);
|
|
657
|
+
const clearPending = async () => {
|
|
658
|
+
const current = await lstat(pendingPath).catch((error) => {
|
|
659
|
+
if (error.code === 'ENOENT')
|
|
660
|
+
return undefined;
|
|
661
|
+
throw error;
|
|
662
|
+
});
|
|
663
|
+
if (!current)
|
|
664
|
+
return;
|
|
665
|
+
if (current.ino !== markerIdentity.ino ||
|
|
666
|
+
current.dev !== markerIdentity.dev ||
|
|
667
|
+
(await readOptionalFile(pendingPath)) !== markerBytes)
|
|
668
|
+
throw new CliError(`Verified commit exists, but the pending marker at ${pendingPath} was replaced; replacement preserved for inspection.`, 2);
|
|
669
|
+
await rm(pendingPath);
|
|
670
|
+
};
|
|
671
|
+
if (reservation.message !== message ||
|
|
672
|
+
JSON.stringify(reservation.paths) !== JSON.stringify(normalized) ||
|
|
673
|
+
!reservation.identity.startsWith(`project-record:${operation}:`))
|
|
674
|
+
throw new CliError(`Unreconciled lifecycle commit marker at ${pendingPath}; preserve it and inspect before retrying.`, 2);
|
|
675
|
+
const result = await commitExactPaths({
|
|
676
|
+
repoRoot,
|
|
677
|
+
paths: normalized,
|
|
678
|
+
message,
|
|
679
|
+
identity: reservation.identity,
|
|
680
|
+
});
|
|
681
|
+
// A public helper repair can settle Git while leaving our reservation. Only
|
|
682
|
+
// its positive prior/current publication proof authorizes retiring that
|
|
683
|
+
// reservation before one fresh generation. Never republish a superseded
|
|
684
|
+
// tree; a migration's finalization remains mandatory.
|
|
685
|
+
if (!settlement &&
|
|
686
|
+
reservationAttempt === 0 &&
|
|
687
|
+
result.outcome === 'failed' &&
|
|
688
|
+
result.settledCommit &&
|
|
689
|
+
result.settledCommit === result.commit) {
|
|
690
|
+
await clearPending();
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
if (settlement &&
|
|
694
|
+
['committed', 'already-matching', 'nothing'].includes(result.outcome) &&
|
|
695
|
+
result.commit !== settlement.commit)
|
|
696
|
+
throw new CliError('Pending migration settlement did not return its verified commit; marker preserved for inspection.', 2);
|
|
697
|
+
if (result.outcome === 'nothing') {
|
|
698
|
+
await clearPending();
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
if ((result.outcome === 'committed' ||
|
|
702
|
+
result.outcome === 'already-matching') &&
|
|
703
|
+
result.commit) {
|
|
704
|
+
try {
|
|
705
|
+
await settlement?.finalize();
|
|
706
|
+
await clearPending();
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
throw new PendingRecordCommitError(errorMessage(error), result);
|
|
710
|
+
}
|
|
711
|
+
return { sha: result.commit };
|
|
712
|
+
}
|
|
713
|
+
throw new PendingRecordCommitError(result.resumable === false
|
|
714
|
+
? `Exact-path lifecycle commit ${result.outcome}: ${result.error ?? 'unverified result'}. Preserve the pending marker at ${pendingPath}${result.receipt ? ` and receipt at ${result.receipt}` : ''} for inspection and explicit provenance reconciliation before any further commit attempt.`
|
|
715
|
+
: `Exact-path lifecycle commit ${result.outcome}: ${result.error ?? 'unverified result'}. Retry after resolving the reported condition with: oat internal commit-paths --identity ${shellQuote(reservation.identity)} --message ${shellQuote(message)} -- ${normalized.map(shellQuote).join(' ')}${result.receipt ? `; receipt ${result.receipt}` : ''}.`, result);
|
|
606
716
|
}
|
|
607
|
-
|
|
608
|
-
cwd: repoRoot,
|
|
609
|
-
});
|
|
610
|
-
const sha = (await git.run(['rev-parse', 'HEAD'], { cwd: repoRoot })).stdout;
|
|
611
|
-
return { sha };
|
|
717
|
+
throw new CliError('Lifecycle commit reservation changed repeatedly; preserve pending state for inspection.', 2);
|
|
612
718
|
}
|
|
613
719
|
export async function preflightSyncedCheckout(target, git) {
|
|
614
720
|
const checkoutExists = await assertCanonicalSyncTargetIdentity(target);
|
|
@@ -888,14 +994,8 @@ export async function pruneSynced(target, git, options) {
|
|
|
888
994
|
if (!options.commit) {
|
|
889
995
|
return { status: 'pruned', lifecycleCommit: null };
|
|
890
996
|
}
|
|
891
|
-
await
|
|
892
|
-
|
|
893
|
-
'-m',
|
|
894
|
-
`chore(oat): prune synced project ${target.slug}`,
|
|
895
|
-
'--',
|
|
896
|
-
relativeRecordPath,
|
|
897
|
-
], { cwd: target.repoRoot });
|
|
898
|
-
const lifecycleCommit = (await git.run(['rev-parse', 'HEAD'], { cwd: target.repoRoot })).stdout;
|
|
997
|
+
const committed = await commitRecordChange(target.repoRoot, [relativeRecordPath], `chore(oat): prune synced project ${target.slug}`, git, { projectRoots: target });
|
|
998
|
+
const lifecycleCommit = committed?.sha ?? null;
|
|
899
999
|
return { status: 'pruned', lifecycleCommit };
|
|
900
1000
|
}
|
|
901
1001
|
}
|
|
@@ -990,10 +1090,206 @@ async function readOptionalFile(path) {
|
|
|
990
1090
|
throw error;
|
|
991
1091
|
}
|
|
992
1092
|
}
|
|
1093
|
+
// Only the existing migration reservation can resume after its source was removed.
|
|
1094
|
+
// No ownership is inferred from the current staged index or a missing directory.
|
|
1095
|
+
async function verifyPendingMigration(target, git, options, sourcePath) {
|
|
1096
|
+
const stop = () => {
|
|
1097
|
+
throw new CliError('Pending migration does not positively match its retained operation, parent source tree and published synced checkout; preserve all state for inspection.', 2);
|
|
1098
|
+
};
|
|
1099
|
+
const sourceRelative = repoRelativePath(target.repoRoot, sourcePath);
|
|
1100
|
+
if (!options.commit ||
|
|
1101
|
+
canonicalizePath(sourcePath) !==
|
|
1102
|
+
resolve(canonicalizePath(target.sharedRoot), target.slug))
|
|
1103
|
+
stop();
|
|
1104
|
+
if (await lstat(sourcePath).then(() => true, (error) => {
|
|
1105
|
+
if (error.code === 'ENOENT')
|
|
1106
|
+
return false;
|
|
1107
|
+
throw error;
|
|
1108
|
+
}))
|
|
1109
|
+
stop();
|
|
1110
|
+
const gitDir = (await git.run(['rev-parse', '--absolute-git-dir'], { cwd: target.repoRoot })).stdout;
|
|
1111
|
+
const pendingDir = join(gitDir, 'oat-record-commit-pending');
|
|
1112
|
+
const message = `chore(oat): migrate ${target.slug} to synced scope`;
|
|
1113
|
+
const candidates = [];
|
|
1114
|
+
for (const name of await readdir(pendingDir).catch((error) => {
|
|
1115
|
+
if (error.code === 'ENOENT')
|
|
1116
|
+
return [];
|
|
1117
|
+
throw error;
|
|
1118
|
+
})) {
|
|
1119
|
+
if (!name.endsWith('.json'))
|
|
1120
|
+
continue;
|
|
1121
|
+
const path = join(pendingDir, name);
|
|
1122
|
+
const file = await open(path, 'r');
|
|
1123
|
+
let bytes;
|
|
1124
|
+
let identity;
|
|
1125
|
+
try {
|
|
1126
|
+
identity = await file.stat();
|
|
1127
|
+
bytes = await file.readFile('utf8');
|
|
1128
|
+
}
|
|
1129
|
+
finally {
|
|
1130
|
+
await file.close();
|
|
1131
|
+
}
|
|
1132
|
+
const marker = JSON.parse(bytes);
|
|
1133
|
+
if (marker.message === message)
|
|
1134
|
+
candidates.push({ name, path, bytes, marker, identity });
|
|
1135
|
+
}
|
|
1136
|
+
if (candidates.length !== 1)
|
|
1137
|
+
stop();
|
|
1138
|
+
const { name, path, bytes, marker, identity } = candidates[0];
|
|
1139
|
+
if (!Array.isArray(marker.paths) ||
|
|
1140
|
+
!marker.paths.every((value) => typeof value === 'string') ||
|
|
1141
|
+
typeof marker.identity !== 'string')
|
|
1142
|
+
stop();
|
|
1143
|
+
const paths = normalizedPathspecs(target.repoRoot, marker.paths);
|
|
1144
|
+
const operation = createHash('sha256')
|
|
1145
|
+
.update(JSON.stringify([message, paths]))
|
|
1146
|
+
.digest('hex');
|
|
1147
|
+
const recordPath = syncedRecordPath(target.syncedRoot, target.slug);
|
|
1148
|
+
const recordRelative = repoRelativePath(target.repoRoot, recordPath);
|
|
1149
|
+
if (name !== `${operation}.json` ||
|
|
1150
|
+
!marker.identity.startsWith(`project-record:${operation}:`) ||
|
|
1151
|
+
JSON.stringify(paths) !== JSON.stringify(marker.paths) ||
|
|
1152
|
+
!paths.includes(recordRelative) ||
|
|
1153
|
+
paths.some((value) => value !== recordRelative &&
|
|
1154
|
+
value !== '.gitignore' &&
|
|
1155
|
+
!value.startsWith(`${sourceRelative}/`)))
|
|
1156
|
+
stop();
|
|
1157
|
+
const receiptPath = join(gitDir, 'oat-exact-path-commits', `${createHash('sha256').update(marker.identity).digest('hex')}.json`);
|
|
1158
|
+
const receiptBytes = await readFile(receiptPath, 'utf8');
|
|
1159
|
+
const receipt = JSON.parse(receiptBytes);
|
|
1160
|
+
if (!receipt.commit ||
|
|
1161
|
+
!receipt.tree ||
|
|
1162
|
+
receipt.identity !== marker.identity ||
|
|
1163
|
+
JSON.stringify(receipt.paths) !== JSON.stringify(paths))
|
|
1164
|
+
stop();
|
|
1165
|
+
const commit = receipt.commit;
|
|
1166
|
+
if (!commit)
|
|
1167
|
+
return stop();
|
|
1168
|
+
const run = async (args, cwd = target.repoRoot) => (await git.run(['--literal-pathspecs', ...args], { cwd })).stdout;
|
|
1169
|
+
if ((await run(['rev-parse', 'HEAD'])) !== commit ||
|
|
1170
|
+
(await run(['show', '-s', '--format=%P', commit])) !== receipt.parent ||
|
|
1171
|
+
(await run(['rev-parse', `${commit}^{tree}`])) !== receipt.tree ||
|
|
1172
|
+
!(await run(['show', '-s', '--format=%B', commit])).includes(`Oat-Operation: ${createHash('sha256').update(marker.identity).digest('hex')}`))
|
|
1173
|
+
stop();
|
|
1174
|
+
const removed = (await run([
|
|
1175
|
+
'ls-tree',
|
|
1176
|
+
'-r',
|
|
1177
|
+
'--name-only',
|
|
1178
|
+
'-z',
|
|
1179
|
+
receipt.parent,
|
|
1180
|
+
'--',
|
|
1181
|
+
sourceRelative,
|
|
1182
|
+
]))
|
|
1183
|
+
.split('\0')
|
|
1184
|
+
.filter(Boolean)
|
|
1185
|
+
.sort();
|
|
1186
|
+
if (!removed.length ||
|
|
1187
|
+
JSON.stringify(removed) !==
|
|
1188
|
+
JSON.stringify(paths.filter((value) => value.startsWith(`${sourceRelative}/`))))
|
|
1189
|
+
stop();
|
|
1190
|
+
if ((await run([
|
|
1191
|
+
'ls-tree',
|
|
1192
|
+
'-r',
|
|
1193
|
+
'--name-only',
|
|
1194
|
+
commit,
|
|
1195
|
+
'--',
|
|
1196
|
+
sourceRelative,
|
|
1197
|
+
])) !== '')
|
|
1198
|
+
stop();
|
|
1199
|
+
const emitted = (await run([
|
|
1200
|
+
'diff-tree',
|
|
1201
|
+
'--root',
|
|
1202
|
+
'--no-commit-id',
|
|
1203
|
+
'-r',
|
|
1204
|
+
'--no-renames',
|
|
1205
|
+
'--name-only',
|
|
1206
|
+
'-z',
|
|
1207
|
+
commit,
|
|
1208
|
+
]))
|
|
1209
|
+
.split('\0')
|
|
1210
|
+
.filter(Boolean);
|
|
1211
|
+
if (emitted.some((value) => !paths.includes(value)))
|
|
1212
|
+
stop();
|
|
1213
|
+
const committedRecord = SyncedProjectRecordSchema.parse(JSON.parse(await run(['show', `${commit}:${recordRelative}`])));
|
|
1214
|
+
const record = await readSyncedRecord(recordPath);
|
|
1215
|
+
if (!record ||
|
|
1216
|
+
record.slug !== target.slug ||
|
|
1217
|
+
record.ref !== target.ref ||
|
|
1218
|
+
record.remote !== target.remote ||
|
|
1219
|
+
record.status !== 'active' ||
|
|
1220
|
+
JSON.stringify(record) !== JSON.stringify(committedRecord))
|
|
1221
|
+
stop();
|
|
1222
|
+
if (!(await assertCanonicalSyncTargetIdentity(target)))
|
|
1223
|
+
return stop();
|
|
1224
|
+
await assertNestedWorktree(target, git);
|
|
1225
|
+
const sha = await headSha(target, git);
|
|
1226
|
+
const remote = await git.run(['ls-remote', '--exit-code', target.remote, target.ref], { cwd: target.repoRoot, allowFailure: true });
|
|
1227
|
+
if (remote.code !== 0 ||
|
|
1228
|
+
remote.stdout.split(/\s+/)[0] !== sha ||
|
|
1229
|
+
(await run(['status', '--porcelain'], target.projectPath)) !== '' ||
|
|
1230
|
+
(await run(['rev-parse', `${receipt.parent}:${sourceRelative}`])) !==
|
|
1231
|
+
(await run(['rev-parse', 'HEAD^{tree}'], target.projectPath)))
|
|
1232
|
+
return stop();
|
|
1233
|
+
const currentMarker = await lstat(path);
|
|
1234
|
+
if (currentMarker.ino !== identity.ino ||
|
|
1235
|
+
currentMarker.dev !== identity.dev ||
|
|
1236
|
+
(await readFile(path, 'utf8')) !== bytes ||
|
|
1237
|
+
(await readFile(receiptPath, 'utf8')) !== receiptBytes)
|
|
1238
|
+
stop();
|
|
1239
|
+
return {
|
|
1240
|
+
commit,
|
|
1241
|
+
parent: receipt.parent,
|
|
1242
|
+
paths,
|
|
1243
|
+
message,
|
|
1244
|
+
sha,
|
|
1245
|
+
sourceRelative,
|
|
1246
|
+
marker: {
|
|
1247
|
+
identity: marker.identity,
|
|
1248
|
+
bytes,
|
|
1249
|
+
ino: identity.ino,
|
|
1250
|
+
dev: identity.dev,
|
|
1251
|
+
},
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
async function finalizePendingMigration(target, git, options, sourcePath) {
|
|
1255
|
+
const { commit, paths, message, sha, sourceRelative, marker } = await verifyPendingMigration(target, git, options, sourcePath);
|
|
1256
|
+
const committed = await commitRecordChange(target.repoRoot, paths, message, git, { projectRoots: target }, {
|
|
1257
|
+
commit,
|
|
1258
|
+
marker,
|
|
1259
|
+
finalize: async () => {
|
|
1260
|
+
const local = await (options.readOatLocalConfig ?? readOatLocalConfig)(target.repoRoot);
|
|
1261
|
+
if (local.activeProject === sourceRelative)
|
|
1262
|
+
await (options.writeOatLocalConfig ?? writeOatLocalConfig)(target.repoRoot, {
|
|
1263
|
+
...local,
|
|
1264
|
+
activeProject: repoRelativePath(target.repoRoot, target.projectPath),
|
|
1265
|
+
});
|
|
1266
|
+
},
|
|
1267
|
+
});
|
|
1268
|
+
if (committed?.sha !== commit)
|
|
1269
|
+
throw new CliError('Pending migration settlement did not return its verified commit; preserve state for inspection.', 2);
|
|
1270
|
+
return {
|
|
1271
|
+
status: 'migrated',
|
|
1272
|
+
lifecycleCommit: commit,
|
|
1273
|
+
sha,
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
993
1276
|
export async function migrateSharedToSynced(target, git, options) {
|
|
994
1277
|
const sourcePath = resolve(options.sourcePath);
|
|
1278
|
+
const sourceExists = await lstat(sourcePath).catch((error) => {
|
|
1279
|
+
if (error.code === 'ENOENT')
|
|
1280
|
+
return undefined;
|
|
1281
|
+
throw error;
|
|
1282
|
+
});
|
|
1283
|
+
if (!sourceExists) {
|
|
1284
|
+
if ((await readSyncedRecord(syncedRecordPath(target.syncedRoot, target.slug))) === null)
|
|
1285
|
+
await assertConfinedMigrationSource(target, sourcePath);
|
|
1286
|
+
return finalizePendingMigration(target, git, options, sourcePath);
|
|
1287
|
+
}
|
|
995
1288
|
await assertConfinedMigrationSource(target, sourcePath);
|
|
996
1289
|
const sourceRelative = repoRelativePath(target.repoRoot, sourcePath);
|
|
1290
|
+
const sourceFiles = (await git.run(['--literal-pathspecs', 'ls-files', '-z', '--', sourceRelative], { cwd: target.repoRoot })).stdout
|
|
1291
|
+
.split('\0')
|
|
1292
|
+
.filter(Boolean);
|
|
997
1293
|
const recordPath = syncedRecordPath(target.syncedRoot, target.slug);
|
|
998
1294
|
const destinationRelative = repoRelativePath(target.repoRoot, target.projectPath);
|
|
999
1295
|
const gitignorePath = join(target.repoRoot, '.gitignore');
|
|
@@ -1030,6 +1326,7 @@ export async function migrateSharedToSynced(target, git, options) {
|
|
|
1030
1326
|
let prePublishRemoteSha = null;
|
|
1031
1327
|
let activeProjectUpdated = false;
|
|
1032
1328
|
let gitignoreSelfHealStarted = false;
|
|
1329
|
+
let migrationCommitPaths;
|
|
1033
1330
|
try {
|
|
1034
1331
|
if (needsGitignoreHeal) {
|
|
1035
1332
|
const repair = await (options.ensureScopedRootGitignore ?? ensureScopedRootGitignore)(target.repoRoot, target.syncedRoot, 'synced', git, 'Cannot add the synced-project rule because .gitignore has staged or unstaged changes; commit or stash those changes, or add the managed rule manually, then retry migration.');
|
|
@@ -1054,7 +1351,12 @@ export async function migrateSharedToSynced(target, git, options) {
|
|
|
1054
1351
|
(await readOptionalFile(gitignorePath)) !== originalGitignore;
|
|
1055
1352
|
let lifecycleCommit = null;
|
|
1056
1353
|
if (options.commit) {
|
|
1057
|
-
|
|
1354
|
+
migrationCommitPaths = [
|
|
1355
|
+
...sourceFiles,
|
|
1356
|
+
recordPath,
|
|
1357
|
+
...(gitignoreChanged ? [gitignorePath] : []),
|
|
1358
|
+
];
|
|
1359
|
+
const committed = await commitRecordChange(target.repoRoot, migrationCommitPaths, `chore(oat): migrate ${target.slug} to synced scope`, git, { projectRoots: target });
|
|
1058
1360
|
lifecycleCommit = committed?.sha ?? null;
|
|
1059
1361
|
await options.afterBranchCommit?.();
|
|
1060
1362
|
}
|
|
@@ -1078,6 +1380,28 @@ export async function migrateSharedToSynced(target, git, options) {
|
|
|
1078
1380
|
return { status: 'migrated', lifecycleCommit, sha: pushed.sha };
|
|
1079
1381
|
}
|
|
1080
1382
|
catch (error) {
|
|
1383
|
+
if (error instanceof PendingRecordCommitError && error.result.commit) {
|
|
1384
|
+
// committed:true is not a provenance proof: the helper can report a
|
|
1385
|
+
// stored receipt's commit before validating it. Reuse the owning verifier
|
|
1386
|
+
// and bind its proof to this invocation before choosing retention.
|
|
1387
|
+
let retained;
|
|
1388
|
+
try {
|
|
1389
|
+
retained = await verifyPendingMigration(target, git, options, sourcePath);
|
|
1390
|
+
if (retained.commit !== error.result.commit ||
|
|
1391
|
+
retained.parent !== preMigrationHead ||
|
|
1392
|
+
!migrationCommitPaths ||
|
|
1393
|
+
JSON.stringify(retained.paths) !==
|
|
1394
|
+
JSON.stringify(normalizedPathspecs(target.repoRoot, migrationCommitPaths)))
|
|
1395
|
+
throw new Error('Retained operation does not match this migration invocation.', { cause: error });
|
|
1396
|
+
}
|
|
1397
|
+
catch (verificationError) {
|
|
1398
|
+
throw new CliError(`${error.message} Migration commit provenance could not be independently verified; no rollback or finalization was attempted. Preserve all current state and recovery metadata for inspection. ${errorMessage(verificationError)}`, 2);
|
|
1399
|
+
}
|
|
1400
|
+
// The verifier proved the removed source is absent. Restore only retained
|
|
1401
|
+
// tree files; the owning finalizer will settle source deletions in the index.
|
|
1402
|
+
const restorePaths = retained.paths.filter((value) => !value.startsWith(`${sourceRelative}/`));
|
|
1403
|
+
throw new CliError(`${error.message} The verified migration commit and published synced checkout were retained. Inspect the hook's changes; removing the hook does not repair its changed bytes. After inspection, restore owned worktree bytes with: git --literal-pathspecs -C ${shellQuote(target.repoRoot)} restore --source ${shellQuote(retained.commit)} --worktree -- ${restorePaths.map(shellQuote).join(' ')}; then finalize with: oat --cwd ${shellQuote(target.repoRoot)} project migrate ${shellQuote(sourceRelative)} --to synced`, error.exitCode);
|
|
1404
|
+
}
|
|
1081
1405
|
const compensationFailures = [];
|
|
1082
1406
|
const compensate = async (resource, recovery, action) => {
|
|
1083
1407
|
try {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents-md-lock.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/agents-md-lock.ts"],"names":[],"mappings":"AAOA,2EAA2E;AAC3E,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CA6I9B"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { lstat, open, unlink } from 'node:fs/promises';
|
|
4
|
+
import { hostname } from 'node:os';
|
|
5
|
+
import { relative } from 'node:path';
|
|
6
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
7
|
+
/** Specific advisory serialization for cooperating AGENTS.md appenders. */
|
|
8
|
+
export async function acquireAgentsMdAppendLock(repoRoot, targetPath) {
|
|
9
|
+
const lockPath = `${targetPath}.oat-guidance.lock`;
|
|
10
|
+
const location = relative(repoRoot, lockPath);
|
|
11
|
+
const recovery = `Check the recorded host/PID and remove ${location} manually only after verifying its owner is gone, then rerun.`;
|
|
12
|
+
const token = randomUUID();
|
|
13
|
+
for (let attempt = 0; attempt < 40; attempt += 1) {
|
|
14
|
+
let handle;
|
|
15
|
+
try {
|
|
16
|
+
handle = await open(lockPath, constants.O_RDWR |
|
|
17
|
+
constants.O_CREAT |
|
|
18
|
+
constants.O_EXCL |
|
|
19
|
+
constants.O_NOFOLLOW |
|
|
20
|
+
constants.O_NONBLOCK, 0o600);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (!(error instanceof Error) ||
|
|
24
|
+
!('code' in error) ||
|
|
25
|
+
error.code !== 'EEXIST') {
|
|
26
|
+
throw new Error(`AGENTS.md append lock could not be acquired. ${recovery}`, { cause: error });
|
|
27
|
+
}
|
|
28
|
+
// Never steal, truncate, or follow a contender's lock, including stale locks.
|
|
29
|
+
let reader;
|
|
30
|
+
try {
|
|
31
|
+
reader = await open(lockPath, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
32
|
+
const stat = await reader.stat();
|
|
33
|
+
if (!stat.isFile() || stat.nlink !== 1 || stat.size > 4096)
|
|
34
|
+
throw new Error('Unsafe lock', { cause: error });
|
|
35
|
+
const content = await reader.readFile('utf8');
|
|
36
|
+
if (content === '' && attempt < 39) {
|
|
37
|
+
await delay(25);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const owner = JSON.parse(content);
|
|
41
|
+
if (typeof owner.token !== 'string' ||
|
|
42
|
+
typeof owner.host !== 'string' ||
|
|
43
|
+
!Number.isSafeInteger(owner.pid) ||
|
|
44
|
+
owner.pid <= 0)
|
|
45
|
+
throw new Error('Malformed lock', { cause: error });
|
|
46
|
+
}
|
|
47
|
+
catch (inspectionError) {
|
|
48
|
+
if (inspectionError instanceof Error &&
|
|
49
|
+
'code' in inspectionError &&
|
|
50
|
+
inspectionError.code === 'ENOENT')
|
|
51
|
+
continue;
|
|
52
|
+
throw new Error(`AGENTS.md append lock is unsafe or malformed. ${recovery}`, { cause: inspectionError });
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
await reader?.close();
|
|
56
|
+
}
|
|
57
|
+
if (attempt < 39)
|
|
58
|
+
await delay(25);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const identity = await handle.stat();
|
|
62
|
+
const bytes = JSON.stringify({ token, host: hostname(), pid: process.pid });
|
|
63
|
+
let releasing;
|
|
64
|
+
let interrupted;
|
|
65
|
+
const onInt = () => {
|
|
66
|
+
interrupted = 'SIGINT';
|
|
67
|
+
};
|
|
68
|
+
const onTerm = () => {
|
|
69
|
+
interrupted = 'SIGTERM';
|
|
70
|
+
};
|
|
71
|
+
const release = () => {
|
|
72
|
+
releasing ??= (async () => {
|
|
73
|
+
process.removeListener('SIGINT', onInt);
|
|
74
|
+
process.removeListener('SIGTERM', onTerm);
|
|
75
|
+
try {
|
|
76
|
+
const current = await lstat(lockPath);
|
|
77
|
+
if (current.isFile() &&
|
|
78
|
+
current.nlink === 1 &&
|
|
79
|
+
current.dev === identity.dev &&
|
|
80
|
+
current.ino === identity.ino &&
|
|
81
|
+
current.size === Buffer.byteLength(bytes) &&
|
|
82
|
+
(await readOwnedBytes()) === bytes) {
|
|
83
|
+
const verified = await lstat(lockPath);
|
|
84
|
+
if (verified.dev === identity.dev && verified.ino === identity.ino)
|
|
85
|
+
await unlink(lockPath);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (!(error instanceof Error) ||
|
|
90
|
+
!('code' in error) ||
|
|
91
|
+
error.code !== 'ENOENT')
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
await handle.close();
|
|
96
|
+
if (interrupted)
|
|
97
|
+
process.kill(process.pid, interrupted);
|
|
98
|
+
}
|
|
99
|
+
})().catch((error) => {
|
|
100
|
+
throw new Error(`AGENTS.md append lock cleanup failed; guidance may already have been written. ${recovery}`, { cause: error });
|
|
101
|
+
});
|
|
102
|
+
return releasing;
|
|
103
|
+
};
|
|
104
|
+
const readOwnedBytes = async () => {
|
|
105
|
+
const buffer = Buffer.alloc(Buffer.byteLength(bytes));
|
|
106
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
107
|
+
return buffer.subarray(0, bytesRead).toString('utf8');
|
|
108
|
+
};
|
|
109
|
+
try {
|
|
110
|
+
await handle.writeFile(bytes);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
// Initialization failed before returning ownership; remove only this inode.
|
|
114
|
+
try {
|
|
115
|
+
const current = await lstat(lockPath);
|
|
116
|
+
if (current.dev === identity.dev && current.ino === identity.ino)
|
|
117
|
+
await unlink(lockPath);
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
await handle.close();
|
|
121
|
+
}
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
// Cleanup runs after the critical section and on catchable termination.
|
|
125
|
+
process.once('SIGINT', onInt);
|
|
126
|
+
process.once('SIGTERM', onTerm);
|
|
127
|
+
return release;
|
|
128
|
+
}
|
|
129
|
+
throw new Error(`AGENTS.md append lock is held by another writer. ${recovery}`);
|
|
130
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents-md.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/agents-md.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,SAAS,EACV,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"agents-md.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/agents-md.ts"],"names":[],"mappings":"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"}
|