ctxr-cli 0.24.0 → 0.25.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/README.md +59 -4
- package/dist/commands/land.d.ts +50 -0
- package/dist/commands/land.d.ts.map +1 -0
- package/dist/commands/land.js +414 -0
- package/dist/commands/land.js.map +1 -0
- package/dist/commands/publish-urls.d.ts +7 -0
- package/dist/commands/publish-urls.d.ts.map +1 -1
- package/dist/commands/publish-urls.js +13 -5
- package/dist/commands/publish-urls.js.map +1 -1
- package/dist/commands/review-list.d.ts +34 -0
- package/dist/commands/review-list.d.ts.map +1 -0
- package/dist/commands/review-list.js +49 -0
- package/dist/commands/review-list.js.map +1 -0
- package/dist/commands/serve.d.ts +3 -6
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +66 -47
- package/dist/commands/serve.js.map +1 -1
- package/dist/config/schema.d.ts +40 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +39 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/core/approval/decision.d.ts +73 -0
- package/dist/core/approval/decision.d.ts.map +1 -0
- package/dist/core/approval/decision.js +166 -0
- package/dist/core/approval/decision.js.map +1 -0
- package/dist/core/approval/file-diff.d.ts +32 -0
- package/dist/core/approval/file-diff.d.ts.map +1 -0
- package/dist/core/approval/file-diff.js +83 -0
- package/dist/core/approval/file-diff.js.map +1 -0
- package/dist/core/approval/identity.d.ts +34 -0
- package/dist/core/approval/identity.d.ts.map +1 -0
- package/dist/core/approval/identity.js +105 -0
- package/dist/core/approval/identity.js.map +1 -0
- package/dist/core/approval/landing-record.d.ts +17 -0
- package/dist/core/approval/landing-record.d.ts.map +1 -0
- package/dist/core/approval/landing-record.js +30 -0
- package/dist/core/approval/landing-record.js.map +1 -0
- package/dist/core/approval/resolver.d.ts +15 -0
- package/dist/core/approval/resolver.d.ts.map +1 -0
- package/dist/core/approval/resolver.js +41 -0
- package/dist/core/approval/resolver.js.map +1 -0
- package/dist/core/approval/sessions.d.ts +36 -0
- package/dist/core/approval/sessions.d.ts.map +1 -0
- package/dist/core/approval/sessions.js +76 -0
- package/dist/core/approval/sessions.js.map +1 -0
- package/dist/core/browse/approve.d.ts +45 -0
- package/dist/core/browse/approve.d.ts.map +1 -0
- package/dist/core/browse/approve.js +239 -0
- package/dist/core/browse/approve.js.map +1 -0
- package/dist/core/browse/review.d.ts +21 -0
- package/dist/core/browse/review.d.ts.map +1 -0
- package/dist/core/browse/review.js +52 -0
- package/dist/core/browse/review.js.map +1 -0
- package/dist/core/errors.d.ts +24 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +55 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/forge/credentials.d.ts +69 -0
- package/dist/core/forge/credentials.d.ts.map +1 -0
- package/dist/core/forge/credentials.js +197 -0
- package/dist/core/forge/credentials.js.map +1 -0
- package/dist/core/forge/github.d.ts +73 -0
- package/dist/core/forge/github.d.ts.map +1 -0
- package/dist/core/forge/github.js +101 -0
- package/dist/core/forge/github.js.map +1 -0
- package/dist/core/git/exec.d.ts +15 -0
- package/dist/core/git/exec.d.ts.map +1 -1
- package/dist/core/git/exec.js +49 -1
- package/dist/core/git/exec.js.map +1 -1
- package/dist/core/graph/diff-block.d.ts.map +1 -1
- package/dist/core/graph/diff-block.js +13 -4
- package/dist/core/graph/diff-block.js.map +1 -1
- package/dist/core/graph/diff-document.d.ts +3 -2
- package/dist/core/graph/diff-document.d.ts.map +1 -1
- package/dist/core/graph/diff-document.js +2 -4
- package/dist/core/graph/diff-document.js.map +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +26 -0
- package/dist/run.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/templates/approve/approve-button.md +1 -0
- package/templates/approve/author-heading.md +1 -0
- package/templates/approve/author-note.md +1 -0
- package/templates/approve/comment-label.md +1 -0
- package/templates/approve/comment-required.md +1 -0
- package/templates/approve/commits-heading.md +1 -0
- package/templates/approve/decision-heading.md +1 -0
- package/templates/approve/decision-note.md +1 -0
- package/templates/approve/diff-binary.md +1 -0
- package/templates/approve/diff-empty.md +1 -0
- package/templates/approve/diff-heading.md +1 -0
- package/templates/approve/facts-heading.md +1 -0
- package/templates/approve/forge-unreachable.md +1 -0
- package/templates/approve/github-app-manifest.json +15 -0
- package/templates/approve/head-missing.md +1 -0
- package/templates/approve/index-empty.md +1 -0
- package/templates/approve/index-heading.md +1 -0
- package/templates/approve/index-intro.md +1 -0
- package/templates/approve/landed.md +1 -0
- package/templates/approve/moved-head.md +1 -0
- package/templates/approve/no-pull.md +1 -0
- package/templates/approve/not-open.md +1 -0
- package/templates/approve/pages-heading.md +1 -0
- package/templates/approve/pages-note.md +1 -0
- package/templates/approve/record-failed.md +1 -0
- package/templates/approve/request-changes-button.md +1 -0
- package/templates/approve/state-line.md +1 -0
- package/templates/graph/knowledge-changes/archived-item.md +1 -0
- package/templates/graph/knowledge-changes/dangling-item.md +1 -0
- package/templates/graph/knowledge-changes/reason-ambiguous.md +1 -0
- package/templates/graph/knowledge-changes/reason-not_found.md +1 -0
- package/templates/graph/review/moved-head.md +1 -0
- package/templates/land/resolver.md +12 -0
- package/templates/serve/style.css +63 -0
- package/templates/skills/ctxr-land.md +37 -31
- package/templates/skills/ctxr-second-opinion/run.mjs +28 -17
- package/templates/skills/ctxr-second-opinion.md +19 -14
- package/templates/skills/ctxr-session-lifecycle.md +9 -0
- package/templates/skills/ctxr-submit.md +4 -2
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { packagedTemplate } from '../templates.js';
|
|
5
|
+
/**
|
|
6
|
+
* write-lifecycle spec: the resolver gets "the conflict playbook the shipped
|
|
7
|
+
* lifecycle skill carries" — read out of that skill's own template, so the
|
|
8
|
+
* playbook has one source and the resolver can never follow an older copy.
|
|
9
|
+
*/
|
|
10
|
+
function lifecyclePlaybook(defaultBranch) {
|
|
11
|
+
const skill = readFileSync(fileURLToPath(new URL('../../../templates/skills/ctxr-session-lifecycle.md', import.meta.url)), 'utf8');
|
|
12
|
+
const start = skill.indexOf('## Conflict playbook');
|
|
13
|
+
if (start === -1)
|
|
14
|
+
throw new Error('the lifecycle skill template carries no "## Conflict playbook" section');
|
|
15
|
+
const next = skill.indexOf('\n## ', start + 1);
|
|
16
|
+
const section = skill.slice(skill.indexOf('\n', start) + 1, next === -1 ? undefined : next).trim();
|
|
17
|
+
return section.replaceAll('__DEFAULT_BRANCH__', defaultBranch);
|
|
18
|
+
}
|
|
19
|
+
export function resolverPrompt(pullNumber, base, defaultBranch) {
|
|
20
|
+
return packagedTemplate('land', 'resolver')
|
|
21
|
+
.replaceAll('__PR__', () => String(pullNumber))
|
|
22
|
+
.replaceAll('__BASE__', () => base)
|
|
23
|
+
.replace('__PLAYBOOK__', () => lifecyclePlaybook(defaultBranch));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* design.md D6: the one place `ctxr land` spawns the store's configured
|
|
27
|
+
* conflict resolver — an argument vector, never a shell string, run in the
|
|
28
|
+
* checkout to resolve, with the prompt as its last argument. Its output goes to
|
|
29
|
+
* `output` (the command's stderr), so stdout keeps the one envelope.
|
|
30
|
+
*/
|
|
31
|
+
export function runResolver(input) {
|
|
32
|
+
const [command, ...args] = input.argv;
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
const child = spawn(command, [...args, input.prompt], { cwd: input.cwd, env: input.env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
35
|
+
child.stdout.on('data', (chunk) => input.output.write(chunk));
|
|
36
|
+
child.stderr.on('data', (chunk) => input.output.write(chunk));
|
|
37
|
+
child.on('error', () => resolve(127));
|
|
38
|
+
child.on('close', (code) => resolve(code ?? 1));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.js","sourceRoot":"","sources":["../../../src/core/approval/resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,aAAqB;IAC9C,MAAM,KAAK,GAAG,YAAY,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,qDAAqD,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACnI,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACpD,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,wEAAwE,CAAC,CAAC;IAC5G,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;IACnG,OAAO,OAAO,CAAC,UAAU,CAAC,oBAAoB,EAAE,aAAa,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,UAAkB,EAAE,IAAY,EAAE,aAAqB;IACpF,OAAO,gBAAgB,CAAC,MAAM,EAAE,UAAU,CAAC;SACxC,UAAU,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;SAC9C,UAAU,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC;SAClC,OAAO,CAAC,cAAc,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAC,CAAC;AACrE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,KAM3B;IACC,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC;IACtC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,OAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,GAAwB,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QACnJ,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QACtE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QACtE,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QACtC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { StoreConfig } from '../../config/schema.js';
|
|
2
|
+
import type { GitRunner } from '../git/exec.js';
|
|
3
|
+
import { type ForgeClient, type PullRequest } from '../forge/github.js';
|
|
4
|
+
import { type ReviewState } from './decision.js';
|
|
5
|
+
export interface SessionWorktree {
|
|
6
|
+
/** The worktree's directory name — the key the review and approval routes use. */
|
|
7
|
+
name: string;
|
|
8
|
+
path: string;
|
|
9
|
+
branch: string;
|
|
10
|
+
}
|
|
11
|
+
export interface SessionPull extends SessionWorktree {
|
|
12
|
+
pull: PullRequest;
|
|
13
|
+
review: ReviewState;
|
|
14
|
+
}
|
|
15
|
+
/** The session worktrees git knows, recognized as `ctxr session list` recognizes them. */
|
|
16
|
+
export declare function sessionWorktrees(git: GitRunner, repoCwd: string, config: StoreConfig): Promise<SessionWorktree[]>;
|
|
17
|
+
/** `origin/<default>` when the repository has that ref, else the local default branch — the review route's own rule. */
|
|
18
|
+
export declare function baseRef(git: GitRunner, cwd: string, defaultBranch: string): Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* write-lifecycle spec (design.md D5): whether `currentHead` carries over the
|
|
21
|
+
* approval of `approvedHead` — each head's commits since it left `base`, compared
|
|
22
|
+
* by patch identity, commit for commit. Anything that cannot be computed (an
|
|
23
|
+
* approved head no longer in this repository) is "no".
|
|
24
|
+
*/
|
|
25
|
+
export declare function carriesOver(git: GitRunner, cwd: string, base: string, approvedHead: string, currentHead: string): Promise<boolean>;
|
|
26
|
+
/** Every forge call here, turned into the usage error the spec asks for when the forge cannot be read. */
|
|
27
|
+
export declare function forgeRead<T>(call: () => Promise<T>): Promise<T>;
|
|
28
|
+
/** One pull request's decision state, with an approval of an earlier head upgraded when the current head carries it over. */
|
|
29
|
+
export declare function reviewStateOf(git: GitRunner, cwd: string, base: string, forge: ForgeClient, recordingLogin: string, pull: PullRequest): Promise<ReviewState>;
|
|
30
|
+
/**
|
|
31
|
+
* change-approval spec: every session worktree whose branch has an open pull
|
|
32
|
+
* request, with its decision state. A worktree with no open pull request is
|
|
33
|
+
* left out.
|
|
34
|
+
*/
|
|
35
|
+
export declare function sessionPulls(git: GitRunner, repoCwd: string, config: StoreConfig, forge: ForgeClient, recordingLogin: string): Promise<SessionPull[]>;
|
|
36
|
+
//# sourceMappingURL=sessions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../../src/core/approval/sessions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGhD,OAAO,EAAqB,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAE3F,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAErE,MAAM,WAAW,eAAe;IAC9B,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAY,SAAQ,eAAe;IAClD,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,0FAA0F;AAC1F,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAQvH;AAED,wHAAwH;AACxH,wBAAsB,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGjG;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUxI;AAED,0GAA0G;AAC1G,wBAAsB,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOrE;AAED,6HAA6H;AAC7H,wBAAsB,aAAa,CACjC,GAAG,EAAE,SAAS,EACd,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,WAAW,EAClB,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,WAAW,CAAC,CAOtB;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,SAAS,EACd,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,WAAW,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,WAAW,EAAE,CAAC,CAYxB"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { ForgeUnreachableError } from '../errors.js';
|
|
3
|
+
import { patchIds } from '../git/exec.js';
|
|
4
|
+
import { listWorktrees } from '../git/worktree.js';
|
|
5
|
+
import { ForgeRequestError } from '../forge/github.js';
|
|
6
|
+
import { isSessionBranch, isSessionWorktreePath } from '../session.js';
|
|
7
|
+
import { resolveReviewState } from './decision.js';
|
|
8
|
+
/** The session worktrees git knows, recognized as `ctxr session list` recognizes them. */
|
|
9
|
+
export async function sessionWorktrees(git, repoCwd, config) {
|
|
10
|
+
const worktrees = await listWorktrees(git, repoCwd);
|
|
11
|
+
return worktrees
|
|
12
|
+
.filter((w) => w.branch !== null && (isSessionBranch(config, w.branch) || isSessionWorktreePath(config, w.path)))
|
|
13
|
+
.map((w) => ({ name: path.basename(w.path), path: w.path, branch: w.branch }));
|
|
14
|
+
}
|
|
15
|
+
/** `origin/<default>` when the repository has that ref, else the local default branch — the review route's own rule. */
|
|
16
|
+
export async function baseRef(git, cwd, defaultBranch) {
|
|
17
|
+
const remote = await git.run(['rev-parse', '--verify', '--quiet', `refs/remotes/origin/${defaultBranch}`], { cwd, allowFailure: true });
|
|
18
|
+
return remote.exitCode === 0 ? `origin/${defaultBranch}` : defaultBranch;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* write-lifecycle spec (design.md D5): whether `currentHead` carries over the
|
|
22
|
+
* approval of `approvedHead` — each head's commits since it left `base`, compared
|
|
23
|
+
* by patch identity, commit for commit. Anything that cannot be computed (an
|
|
24
|
+
* approved head no longer in this repository) is "no".
|
|
25
|
+
*/
|
|
26
|
+
export async function carriesOver(git, cwd, base, approvedHead, currentHead) {
|
|
27
|
+
try {
|
|
28
|
+
const forkOf = async (head) => (await git.run(['merge-base', head, base], { cwd })).stdout.trim();
|
|
29
|
+
const before = await patchIds(cwd, `${await forkOf(approvedHead)}..${approvedHead}`);
|
|
30
|
+
const after = await patchIds(cwd, `${await forkOf(currentHead)}..${currentHead}`);
|
|
31
|
+
return before.length > 0 && before.length === after.length && before.every((id, i) => id === after[i]);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Every forge call here, turned into the usage error the spec asks for when the forge cannot be read. */
|
|
38
|
+
export async function forgeRead(call) {
|
|
39
|
+
try {
|
|
40
|
+
return await call();
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (err instanceof ForgeRequestError)
|
|
44
|
+
throw new ForgeUnreachableError(err.message);
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** One pull request's decision state, with an approval of an earlier head upgraded when the current head carries it over. */
|
|
49
|
+
export async function reviewStateOf(git, cwd, base, forge, recordingLogin, pull) {
|
|
50
|
+
const [reviews, comments] = await Promise.all([forge.reviews(pull.number), forge.comments(pull.number)]);
|
|
51
|
+
const state = resolveReviewState({ reviews, comments, recordingLogin, head: pull.headSha });
|
|
52
|
+
if (state.approvalNeedsCarryCheck && state.decision !== null && (await carriesOver(git, cwd, base, state.decision.head, pull.headSha))) {
|
|
53
|
+
return { ...state, state: 'approved', approvalNeedsCarryCheck: false };
|
|
54
|
+
}
|
|
55
|
+
return state;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* change-approval spec: every session worktree whose branch has an open pull
|
|
59
|
+
* request, with its decision state. A worktree with no open pull request is
|
|
60
|
+
* left out.
|
|
61
|
+
*/
|
|
62
|
+
export async function sessionPulls(git, repoCwd, config, forge, recordingLogin) {
|
|
63
|
+
const worktrees = await sessionWorktrees(git, repoCwd, config);
|
|
64
|
+
const open = await forgeRead(() => forge.openPulls());
|
|
65
|
+
const base = await baseRef(git, repoCwd, config.git.default_branch);
|
|
66
|
+
const result = [];
|
|
67
|
+
for (const worktree of worktrees) {
|
|
68
|
+
const pull = open.find((p) => p.headRef === worktree.branch);
|
|
69
|
+
if (pull === undefined)
|
|
70
|
+
continue;
|
|
71
|
+
const review = await forgeRead(() => reviewStateOf(git, worktree.path, base, forge, recordingLogin, pull));
|
|
72
|
+
result.push({ ...worktree, pull, review });
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../../src/core/approval/sessions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
import type { ChangeApprovalConfig } from '../../config/schema.js';
|
|
3
|
+
import type { IdentityVerifier } from '../approval/identity.js';
|
|
4
|
+
import type { ForgeClient } from '../forge/github.js';
|
|
5
|
+
import type { GitRunner } from '../git/exec.js';
|
|
6
|
+
import type { Store } from '../store.js';
|
|
7
|
+
import type { LinkResolver } from './link-resolver.js';
|
|
8
|
+
import { type RenderedBody } from './render.js';
|
|
9
|
+
/**
|
|
10
|
+
* change-approval spec: the approval index, one session's approval page, and
|
|
11
|
+
* the one route on this server that admits `POST`. Every path under the prefix
|
|
12
|
+
* is answered here, and only after the identity assertion verifies — nothing
|
|
13
|
+
* about the store or the forge is read before that.
|
|
14
|
+
*/
|
|
15
|
+
export declare const APPROVE_ROUTE_PREFIX = "/approve/";
|
|
16
|
+
export interface ApprovalServer {
|
|
17
|
+
approval: ChangeApprovalConfig;
|
|
18
|
+
/** The origin of `serve.base_url`, which a decision's `Origin` header must equal (design.md D9). */
|
|
19
|
+
origin: string;
|
|
20
|
+
forge: ForgeClient;
|
|
21
|
+
verifier: IdentityVerifier;
|
|
22
|
+
git: GitRunner;
|
|
23
|
+
now(): Date;
|
|
24
|
+
}
|
|
25
|
+
/** What a route needs from the browsing surface once the requester is known: the shell, and note links. */
|
|
26
|
+
export interface PageKit {
|
|
27
|
+
page(title: string, body: RenderedBody): Promise<string>;
|
|
28
|
+
linkFor(notePath: string): string | null;
|
|
29
|
+
resolveLink: LinkResolver;
|
|
30
|
+
}
|
|
31
|
+
export interface Reply {
|
|
32
|
+
status: number;
|
|
33
|
+
contentType: string;
|
|
34
|
+
body: string;
|
|
35
|
+
headers?: Record<string, string>;
|
|
36
|
+
}
|
|
37
|
+
export declare function isApprovePath(pathname: string): boolean;
|
|
38
|
+
export declare function isDecisionRoute(pathname: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Answers every request under the approval prefix. `server` is null when the
|
|
41
|
+
* store declares no change approval, and then every such path is `404`.
|
|
42
|
+
* `makeKit` builds the browsing surface's shell only after the requester is known.
|
|
43
|
+
*/
|
|
44
|
+
export declare function handleApprove(store: Store, server: ApprovalServer | null, req: IncomingMessage, method: string, pathname: string, makeKit: () => Promise<PageKit>): Promise<Reply>;
|
|
45
|
+
//# sourceMappingURL=approve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approve.d.ts","sourceRoot":"","sources":["../../../src/core/browse/approve.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAInE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAIhE,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,oBAAoB,CAAC;AAEnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAA8B,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAG5E;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,cAAc,CAAC;AAOhD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,oGAAoG;IACpG,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,GAAG,EAAE,SAAS,CAAC;IACf,GAAG,IAAI,IAAI,CAAC;CACb;AAED,2GAA2G;AAC3G,MAAM,WAAW,OAAO;IACtB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzD,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACzC,WAAW,EAAE,YAAY,CAAC;CAC3B;AAED,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAQD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEzD;AAwND;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,cAAc,GAAG,IAAI,EAC7B,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAC9B,OAAO,CAAC,KAAK,CAAC,CAgBhB"}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { pageUrlsFor } from '../../commands/publish-urls.js';
|
|
3
|
+
import { decisionBody, isDecider } from '../approval/decision.js';
|
|
4
|
+
import { readFileDiffs, renderFileDiffs } from '../approval/file-diff.js';
|
|
5
|
+
import { readLandingRecord } from '../approval/landing-record.js';
|
|
6
|
+
import { baseRef, forgeRead, reviewStateOf, sessionPulls, sessionWorktrees } from '../approval/sessions.js';
|
|
7
|
+
import { ForgeUnreachableError } from '../errors.js';
|
|
8
|
+
import { ForgeRequestError } from '../forge/github.js';
|
|
9
|
+
import { storePrefix } from '../graph/tree.js';
|
|
10
|
+
import { packagedTemplate } from '../templates.js';
|
|
11
|
+
import { escapeHtml, renderNoteBody } from './render.js';
|
|
12
|
+
import { renderReview } from './review.js';
|
|
13
|
+
/**
|
|
14
|
+
* change-approval spec: the approval index, one session's approval page, and
|
|
15
|
+
* the one route on this server that admits `POST`. Every path under the prefix
|
|
16
|
+
* is answered here, and only after the identity assertion verifies — nothing
|
|
17
|
+
* about the store or the forge is read before that.
|
|
18
|
+
*/
|
|
19
|
+
export const APPROVE_ROUTE_PREFIX = '/approve/';
|
|
20
|
+
const DECISION_ROUTE = /^\/approve\/([^/]+)\/decision$/;
|
|
21
|
+
const PAGE_ROUTE = /^\/approve\/([^/]+)\/?$/;
|
|
22
|
+
const SHA = /^[0-9a-f]{40}$/;
|
|
23
|
+
const MAX_FORM_BYTES = 64 * 1024;
|
|
24
|
+
const MAX_COMMENT_CHARS = 20_000;
|
|
25
|
+
const text = (status, message) => ({ status, contentType: 'text/plain; charset=utf-8', body: `${message}\n` });
|
|
26
|
+
function copy(name) {
|
|
27
|
+
return packagedTemplate('approve', name);
|
|
28
|
+
}
|
|
29
|
+
export function isApprovePath(pathname) {
|
|
30
|
+
return pathname === '/approve' || pathname.startsWith(APPROVE_ROUTE_PREFIX);
|
|
31
|
+
}
|
|
32
|
+
export function isDecisionRoute(pathname) {
|
|
33
|
+
return DECISION_ROUTE.test(pathname);
|
|
34
|
+
}
|
|
35
|
+
function approveRoute(name) {
|
|
36
|
+
return `${APPROVE_ROUTE_PREFIX}${encodeURIComponent(name)}/`;
|
|
37
|
+
}
|
|
38
|
+
const STATE_LABEL = {
|
|
39
|
+
awaiting: 'awaiting a decision',
|
|
40
|
+
approved: 'approved, landing shortly',
|
|
41
|
+
'changes-requested': 'changes requested',
|
|
42
|
+
'needs-reapproval': 'needs a new decision',
|
|
43
|
+
blocked: 'blocked',
|
|
44
|
+
};
|
|
45
|
+
async function readForm(req) {
|
|
46
|
+
const chunks = [];
|
|
47
|
+
let size = 0;
|
|
48
|
+
for await (const chunk of req) {
|
|
49
|
+
size += chunk.length;
|
|
50
|
+
if (size > MAX_FORM_BYTES)
|
|
51
|
+
return null;
|
|
52
|
+
chunks.push(chunk);
|
|
53
|
+
}
|
|
54
|
+
return new URLSearchParams(Buffer.concat(chunks).toString('utf8'));
|
|
55
|
+
}
|
|
56
|
+
async function locate(store, server, name) {
|
|
57
|
+
const worktree = (await sessionWorktrees(server.git, store.root, store.config)).find((w) => w.name === name);
|
|
58
|
+
if (worktree === undefined)
|
|
59
|
+
return null;
|
|
60
|
+
const open = await forgeRead(() => server.forge.openPulls());
|
|
61
|
+
return { name, worktreePath: worktree.path, branch: worktree.branch, pull: open.find((p) => p.headRef === worktree.branch) ?? null };
|
|
62
|
+
}
|
|
63
|
+
async function indexPage(store, server, kit) {
|
|
64
|
+
const html = [`<h1>${escapeHtml(copy('index-heading'))}</h1>`];
|
|
65
|
+
try {
|
|
66
|
+
const pulls = await sessionPulls(server.git, store.root, store.config, server.forge, server.approval.recording_login);
|
|
67
|
+
if (pulls.length === 0) {
|
|
68
|
+
html.push(`<p class="ctxr-empty">${escapeHtml(copy('index-empty'))}</p>`);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
html.push(`<p>${escapeHtml(copy('index-intro'))}</p>`);
|
|
72
|
+
html.push('<table class="ctxr-approve-index"><thead><tr><th>Change</th><th>Pull request</th><th>Commit</th><th>State</th></tr></thead><tbody>');
|
|
73
|
+
for (const p of pulls) {
|
|
74
|
+
html.push(`<tr><td><a href="${approveRoute(p.name)}">${escapeHtml(p.name)}</a></td><td>#${p.pull.number}</td>` +
|
|
75
|
+
`<td><code>${escapeHtml(p.pull.headSha.slice(0, 7))}</code></td><td>${escapeHtml(STATE_LABEL[p.review.state])}</td></tr>`);
|
|
76
|
+
}
|
|
77
|
+
html.push('</tbody></table>');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
if (!(err instanceof ForgeUnreachableError))
|
|
82
|
+
throw err;
|
|
83
|
+
html.push(`<p class="ctxr-approve-notice">${escapeHtml(copy('forge-unreachable'))}</p>`);
|
|
84
|
+
}
|
|
85
|
+
return { status: 200, contentType: 'text/html; charset=utf-8', body: await kit.page('approve', { html: html.join('\n'), hasDiagram: false }) };
|
|
86
|
+
}
|
|
87
|
+
async function renderChangePage(store, server, kit, name, notice = {}) {
|
|
88
|
+
const title = `approve — ${name}`;
|
|
89
|
+
const shell = async (status, parts, hasDiagram = false) => ({
|
|
90
|
+
status,
|
|
91
|
+
contentType: 'text/html; charset=utf-8',
|
|
92
|
+
body: await kit.page(title, { html: parts.join('\n'), hasDiagram }),
|
|
93
|
+
});
|
|
94
|
+
const html = [`<h1>${escapeHtml(name)}</h1>`];
|
|
95
|
+
if (notice.message !== undefined)
|
|
96
|
+
html.push(`<p class="ctxr-approve-notice">${escapeHtml(notice.message)}</p>`);
|
|
97
|
+
let located;
|
|
98
|
+
try {
|
|
99
|
+
located = await locate(store, server, name);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
if (!(err instanceof ForgeUnreachableError))
|
|
103
|
+
throw err;
|
|
104
|
+
return shell(notice.status ?? 200, [...html, `<p class="ctxr-approve-notice">${escapeHtml(copy('forge-unreachable'))}</p>`]);
|
|
105
|
+
}
|
|
106
|
+
if (located === null) {
|
|
107
|
+
const record = await readLandingRecord(store.root, name);
|
|
108
|
+
if (record === null)
|
|
109
|
+
return text(404, 'not found');
|
|
110
|
+
const landed = escapeHtml(copy('landed'))
|
|
111
|
+
.replace('__PR__', () => String(record.pull_request))
|
|
112
|
+
.replace('__COMMIT__', () => `<code>${escapeHtml(record.merged_commit ?? 'unknown')}</code>`);
|
|
113
|
+
return shell(200, [...html, `<p>${landed}</p>`]);
|
|
114
|
+
}
|
|
115
|
+
if (located.pull === null)
|
|
116
|
+
return shell(notice.status ?? 200, [...html, `<p class="ctxr-empty">${escapeHtml(copy('no-pull'))}</p>`]);
|
|
117
|
+
const pull = located.pull;
|
|
118
|
+
const head = pull.headSha;
|
|
119
|
+
if (notice.movedFrom !== undefined) {
|
|
120
|
+
const moved = escapeHtml(copy('moved-head'))
|
|
121
|
+
.replace('__RECORDED__', () => `<code>${escapeHtml(notice.movedFrom)}</code>`)
|
|
122
|
+
.replace('__CURRENT__', () => `<code>${escapeHtml(head)}</code>`);
|
|
123
|
+
html.push(`<p class="ctxr-review-moved">${moved}</p>`);
|
|
124
|
+
}
|
|
125
|
+
const present = await server.git.run(['cat-file', '-e', `${head}^{commit}`], { cwd: located.worktreePath, allowFailure: true });
|
|
126
|
+
if (present.exitCode !== 0) {
|
|
127
|
+
return shell(notice.status ?? 200, [...html, `<p class="ctxr-approve-notice">${escapeHtml(copy('head-missing')).replace('__HEAD__', () => `<code>${escapeHtml(head)}</code>`)}</p>`]);
|
|
128
|
+
}
|
|
129
|
+
const base = await baseRef(server.git, located.worktreePath, store.config.git.default_branch);
|
|
130
|
+
const prefix = await storePrefix(server.git, store.root);
|
|
131
|
+
const worktreeStoreRoot = path.join(located.worktreePath, prefix);
|
|
132
|
+
const state = await forgeRead(() => reviewStateOf(server.git, located.worktreePath, base, server.forge, server.approval.recording_login, pull));
|
|
133
|
+
let hasDiagram = false;
|
|
134
|
+
html.push(`<p>Pull request <a href="${escapeHtml(pull.url)}">#${pull.number}</a>, commit <code>${escapeHtml(head)}</code>. ${escapeHtml(copy('state-line').replace('__STATE__', STATE_LABEL[state.state]))}</p>`);
|
|
135
|
+
// The author's account: labelled as such, and kept apart from everything computed below.
|
|
136
|
+
const authorBody = renderNoteBody(pull.body, kit.resolveLink);
|
|
137
|
+
hasDiagram ||= authorBody.hasDiagram;
|
|
138
|
+
const log = await server.git.run(['log', '--reverse', '--no-color', '--format=%H%x1f%B%x1e', `${base}..${head}`], { cwd: located.worktreePath });
|
|
139
|
+
const commits = log.stdout
|
|
140
|
+
.split('\x1e')
|
|
141
|
+
.map((entry) => entry.trim())
|
|
142
|
+
.filter(Boolean)
|
|
143
|
+
.map((entry) => {
|
|
144
|
+
const [sha, message] = entry.split('\x1f');
|
|
145
|
+
return `<li><code>${escapeHtml(sha.slice(0, 7))}</code><pre class="ctxr-commit-message">${escapeHtml(message.trim())}</pre></li>`;
|
|
146
|
+
});
|
|
147
|
+
html.push(`<section class="ctxr-author-account"><h2>${escapeHtml(copy('author-heading'))}</h2>`, `<p class="ctxr-summary">${escapeHtml(copy('author-note'))}</p>`, `<h3>${escapeHtml(pull.title)}</h3>`, authorBody.html, `<h3>${escapeHtml(copy('commits-heading'))}</h3><ol class="ctxr-commits">${commits.join('\n')}</ol>`, '</section>');
|
|
148
|
+
const facts = await renderReview(store, name, null, kit.linkFor, kit.resolveLink, head);
|
|
149
|
+
hasDiagram ||= facts.hasDiagram;
|
|
150
|
+
html.push(`<section class="ctxr-derived"><h2>${escapeHtml(copy('facts-heading'))}</h2>`, facts.html, '</section>');
|
|
151
|
+
const pages = await pageUrlsFor({ root: worktreeStoreRoot, config: store.config }, [`${base}...${head}`]);
|
|
152
|
+
if (pages.length > 0) {
|
|
153
|
+
const link = (address) => address === null ? '—' : `<a href="${escapeHtml(address.url ?? address.route)}">${escapeHtml(address.url ?? address.route)}</a>`;
|
|
154
|
+
const rows = pages.map((p) => `<tr><td>${escapeHtml(p.status)}</td><td>${escapeHtml(p.lifespan)}</td><td><code>${escapeHtml(p.path)}</code></td>` +
|
|
155
|
+
`<td>${p.status === 'removed' ? '—' : link(p.preview)}</td><td>${link(p.published)}</td></tr>`);
|
|
156
|
+
html.push(`<section class="ctxr-pages"><h2>${escapeHtml(copy('pages-heading'))}</h2><p class="ctxr-summary">${escapeHtml(copy('pages-note'))}</p>`, `<table><thead><tr><th>Status</th><th>Lifespan</th><th>Page</th><th>Preview</th><th>Published</th></tr></thead><tbody>${rows.join('')}</tbody></table></section>`);
|
|
157
|
+
}
|
|
158
|
+
const files = await readFileDiffs(server.git, worktreeStoreRoot, base, head, store.config.derived.paths);
|
|
159
|
+
html.push(`<section class="ctxr-changed-files"><h2>${escapeHtml(copy('diff-heading'))}</h2>`, renderFileDiffs(files, { binary: copy('diff-binary'), noFiles: copy('diff-empty') }), '</section>');
|
|
160
|
+
const action = `${approveRoute(name)}decision`;
|
|
161
|
+
const note = escapeHtml(copy('decision-note')).replace('__HEAD__', () => `<code>${escapeHtml(head.slice(0, 7))}</code>`);
|
|
162
|
+
html.push(`<section class="ctxr-decision"><h2>${escapeHtml(copy('decision-heading'))}</h2><p>${note}</p>`, `<form method="post" action="${action}"><input type="hidden" name="decision" value="approve"><input type="hidden" name="head" value="${head}"><button type="submit">${escapeHtml(copy('approve-button'))}</button></form>`, `<form method="post" action="${action}"><input type="hidden" name="decision" value="request-changes"><input type="hidden" name="head" value="${head}">` +
|
|
163
|
+
`<label for="ctxr-comment">${escapeHtml(copy('comment-label'))}</label><textarea id="ctxr-comment" name="comment" rows="5" required></textarea>` +
|
|
164
|
+
`<button type="submit">${escapeHtml(copy('request-changes-button'))}</button></form>`, '</section>');
|
|
165
|
+
return shell(notice.status ?? 200, html, hasDiagram);
|
|
166
|
+
}
|
|
167
|
+
async function decide(store, server, kit, req, name, email) {
|
|
168
|
+
if (req.headers.origin !== server.origin)
|
|
169
|
+
return text(403, 'cross-site decision refused');
|
|
170
|
+
const form = await readForm(req);
|
|
171
|
+
if (form === null)
|
|
172
|
+
return text(413, 'decision too large');
|
|
173
|
+
const decision = form.get('decision');
|
|
174
|
+
const head = form.get('head') ?? '';
|
|
175
|
+
const comment = (form.get('comment') ?? '').trim();
|
|
176
|
+
if ((decision !== 'approve' && decision !== 'request-changes') || !SHA.test(head))
|
|
177
|
+
return text(400, 'malformed decision');
|
|
178
|
+
if (comment.length > MAX_COMMENT_CHARS)
|
|
179
|
+
return text(413, 'comment too long');
|
|
180
|
+
let located;
|
|
181
|
+
try {
|
|
182
|
+
located = await locate(store, server, name);
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
if (!(err instanceof ForgeUnreachableError))
|
|
186
|
+
throw err;
|
|
187
|
+
return renderChangePage(store, server, kit, name, { status: 502, message: copy('record-failed') });
|
|
188
|
+
}
|
|
189
|
+
if (located === null)
|
|
190
|
+
return text(404, 'not found');
|
|
191
|
+
if (located.pull === null || located.pull.state !== 'open')
|
|
192
|
+
return renderChangePage(store, server, kit, name, { status: 409, message: copy('not-open') });
|
|
193
|
+
if (located.pull.headSha !== head)
|
|
194
|
+
return renderChangePage(store, server, kit, name, { status: 409, movedFrom: head });
|
|
195
|
+
if (decision === 'request-changes' && comment === '')
|
|
196
|
+
return renderChangePage(store, server, kit, name, { status: 400, message: copy('comment-required') });
|
|
197
|
+
const kind = decision;
|
|
198
|
+
try {
|
|
199
|
+
await server.forge.createReview(located.pull.number, {
|
|
200
|
+
event: kind === 'approve' ? 'APPROVE' : 'REQUEST_CHANGES',
|
|
201
|
+
commitId: head,
|
|
202
|
+
body: decisionBody({ decision: kind, head, by: email, at: server.now().toISOString(), carriedFrom: null, comment: kind === 'approve' ? null : comment }),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
if (!(err instanceof ForgeRequestError))
|
|
207
|
+
throw err;
|
|
208
|
+
return renderChangePage(store, server, kit, name, { status: 502, message: copy('record-failed') });
|
|
209
|
+
}
|
|
210
|
+
// Post/redirect/get: the page the decider lands on reads the recorded state back from the forge.
|
|
211
|
+
return { status: 303, contentType: 'text/plain; charset=utf-8', body: 'recorded\n', headers: { Location: approveRoute(name) } };
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Answers every request under the approval prefix. `server` is null when the
|
|
215
|
+
* store declares no change approval, and then every such path is `404`.
|
|
216
|
+
* `makeKit` builds the browsing surface's shell only after the requester is known.
|
|
217
|
+
*/
|
|
218
|
+
export async function handleApprove(store, server, req, method, pathname, makeKit) {
|
|
219
|
+
if (server === null)
|
|
220
|
+
return text(404, 'not found');
|
|
221
|
+
const verified = await server.verifier.verify(req.headers[server.approval.identity.header.toLowerCase()]);
|
|
222
|
+
if (!verified.ok)
|
|
223
|
+
return text(401, 'a verified identity assertion is required');
|
|
224
|
+
if (!isDecider(verified.email, server.approval.deciders))
|
|
225
|
+
return text(403, 'this identity may not decide on changes here');
|
|
226
|
+
const decisionMatch = DECISION_ROUTE.exec(pathname);
|
|
227
|
+
if (method === 'POST') {
|
|
228
|
+
if (decisionMatch === null)
|
|
229
|
+
return text(405, 'method not allowed');
|
|
230
|
+
return decide(store, server, await makeKit(), req, decisionMatch[1], verified.email);
|
|
231
|
+
}
|
|
232
|
+
if (pathname === '/approve' || pathname === APPROVE_ROUTE_PREFIX)
|
|
233
|
+
return indexPage(store, server, await makeKit());
|
|
234
|
+
const pageMatch = PAGE_ROUTE.exec(pathname);
|
|
235
|
+
if (pageMatch === null)
|
|
236
|
+
return text(404, 'not found');
|
|
237
|
+
return renderChangePage(store, server, await makeKit(), pageMatch[1]);
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=approve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approve.js","sourceRoot":"","sources":["../../../src/core/browse/approve.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Store } from '../store.js';
|
|
2
|
+
import type { LinkResolver } from './link-resolver.js';
|
|
3
|
+
import { type RenderedBody } from './render.js';
|
|
4
|
+
/**
|
|
5
|
+
* context-browsing spec (review-the-knowledge-diff D5, D6): one session
|
|
6
|
+
* worktree's knowledge changes, derived from the delta `ctxr graph diff`
|
|
7
|
+
* computes for that worktree's `HEAD` against the default branch — `origin/`
|
|
8
|
+
* plus it when that ref exists, else the local branch, and never a fetch.
|
|
9
|
+
* Nothing an agent wrote about the change is read: no commit message, no pull
|
|
10
|
+
* request body, no file the change adds for the purpose.
|
|
11
|
+
*
|
|
12
|
+
* `until` names the head to compare (the approval page passes the pull
|
|
13
|
+
* request's head); the worktree's `HEAD` otherwise.
|
|
14
|
+
*
|
|
15
|
+
* Both sides come from git's object store, so the worktree's uncommitted work
|
|
16
|
+
* is neither shown nor touched. A path links to the note route only when this
|
|
17
|
+
* server's note route answers for it, so a worktree-only note stays text and
|
|
18
|
+
* the view widens nothing the note route serves.
|
|
19
|
+
*/
|
|
20
|
+
export declare function renderReview(store: Store, worktree: string, recordedHead: string | null, linkFor: (notePath: string) => string | null, resolveLink: LinkResolver, until?: string): Promise<RenderedBody>;
|
|
21
|
+
//# sourceMappingURL=review.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../../src/core/browse/review.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAA8B,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAE5E;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,YAAY,CAChC,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,EAC5C,WAAW,EAAE,YAAY,EACzB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,YAAY,CAAC,CA4BvB"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { createExecFileGitRunner } from '../git/exec.js';
|
|
3
|
+
import { renderDiffParts } from '../graph/diff-document.js';
|
|
4
|
+
import { computeGraphDelta, resolveRange } from '../graph/diff.js';
|
|
5
|
+
import { storePrefix } from '../graph/tree.js';
|
|
6
|
+
import { packagedTemplate } from '../templates.js';
|
|
7
|
+
import { escapeHtml, renderNoteBody } from './render.js';
|
|
8
|
+
/**
|
|
9
|
+
* context-browsing spec (review-the-knowledge-diff D5, D6): one session
|
|
10
|
+
* worktree's knowledge changes, derived from the delta `ctxr graph diff`
|
|
11
|
+
* computes for that worktree's `HEAD` against the default branch — `origin/`
|
|
12
|
+
* plus it when that ref exists, else the local branch, and never a fetch.
|
|
13
|
+
* Nothing an agent wrote about the change is read: no commit message, no pull
|
|
14
|
+
* request body, no file the change adds for the purpose.
|
|
15
|
+
*
|
|
16
|
+
* `until` names the head to compare (the approval page passes the pull
|
|
17
|
+
* request's head); the worktree's `HEAD` otherwise.
|
|
18
|
+
*
|
|
19
|
+
* Both sides come from git's object store, so the worktree's uncommitted work
|
|
20
|
+
* is neither shown nor touched. A path links to the note route only when this
|
|
21
|
+
* server's note route answers for it, so a worktree-only note stays text and
|
|
22
|
+
* the view widens nothing the note route serves.
|
|
23
|
+
*/
|
|
24
|
+
export async function renderReview(store, worktree, recordedHead, linkFor, resolveLink, until) {
|
|
25
|
+
const git = createExecFileGitRunner();
|
|
26
|
+
const prefix = await storePrefix(git, store.root);
|
|
27
|
+
const worktreeStoreRoot = path.join(store.root, store.config.session.worktrees_path, worktree, prefix);
|
|
28
|
+
const defaultBranch = store.config.git.default_branch;
|
|
29
|
+
const remote = await git.run(['rev-parse', '--verify', '--quiet', `refs/remotes/origin/${defaultBranch}`], {
|
|
30
|
+
cwd: worktreeStoreRoot,
|
|
31
|
+
allowFailure: true,
|
|
32
|
+
});
|
|
33
|
+
const since = remote.exitCode === 0 ? `origin/${defaultBranch}` : defaultBranch;
|
|
34
|
+
const range = await resolveRange(git, worktreeStoreRoot, since, until);
|
|
35
|
+
const delta = await computeGraphDelta(git, worktreeStoreRoot, range, store.config);
|
|
36
|
+
const html = [];
|
|
37
|
+
if (recordedHead !== null && recordedHead !== delta.head) {
|
|
38
|
+
const notice = escapeHtml(packagedTemplate('graph/review', 'moved-head'))
|
|
39
|
+
.replace('__RECORDED__', () => `<code>${escapeHtml(recordedHead)}</code>`)
|
|
40
|
+
.replace('__CURRENT__', () => `<code>${escapeHtml(delta.head)}</code>`);
|
|
41
|
+
html.push(`<p class="ctxr-review-moved">${notice}</p>`);
|
|
42
|
+
}
|
|
43
|
+
// render-diagrams-in-notes: the delta's diagram draws like a note's, so the page carries the renderer when any part has one.
|
|
44
|
+
let hasDiagram = false;
|
|
45
|
+
for (const part of renderDiffParts(delta, { linkFor, diagram: true })) {
|
|
46
|
+
const rendered = renderNoteBody(part.text, resolveLink);
|
|
47
|
+
hasDiagram ||= rendered.hasDiagram;
|
|
48
|
+
html.push(part.kind === 'markdown' ? rendered.html : `<details><summary>${escapeHtml(part.summary)}</summary>\n${rendered.html}</details>\n`);
|
|
49
|
+
}
|
|
50
|
+
return { html: html.join('\n'), hasDiagram };
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=review.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.js","sourceRoot":"","sources":["../../../src/core/browse/review.ts"],"names":[],"mappings":"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"}
|
package/dist/core/errors.d.ts
CHANGED
|
@@ -280,4 +280,28 @@ export declare class GraphDiffNoMergeBaseError extends ContextureError {
|
|
|
280
280
|
export declare class GraphDiffFormatError extends ContextureError {
|
|
281
281
|
constructor(given: string, accepted: readonly string[], withJson: boolean);
|
|
282
282
|
}
|
|
283
|
+
/** change-approval spec: the review and landing commands read decisions only a store that declares change approval has. */
|
|
284
|
+
export declare class ChangeApprovalNotDeclaredError extends ContextureError {
|
|
285
|
+
constructor(command: string);
|
|
286
|
+
}
|
|
287
|
+
/** change-approval spec: an unreachable forge is reported as such, never as every pull request awaiting a decision. */
|
|
288
|
+
export declare class ForgeUnreachableError extends ContextureError {
|
|
289
|
+
constructor(detail: string);
|
|
290
|
+
}
|
|
291
|
+
/** change-approval spec (design.md D4): serve records decisions only as the identity the store names. */
|
|
292
|
+
export declare class RecordingLoginMismatchError extends ContextureError {
|
|
293
|
+
constructor(expected: string, actual: string);
|
|
294
|
+
}
|
|
295
|
+
/** write-lifecycle spec: a refusal before any side effect — the default branch as head, a pull request that is not open, an unknown target. */
|
|
296
|
+
export declare class LandRefusedError extends ContextureError {
|
|
297
|
+
constructor(reason: string, message: string, details?: Record<string, unknown>);
|
|
298
|
+
}
|
|
299
|
+
/** write-lifecycle spec: `--pending` lands only recorded approvals, so `--confirm` cannot ride along. */
|
|
300
|
+
export declare class LandFlagsError extends ContextureError {
|
|
301
|
+
constructor(message: string);
|
|
302
|
+
}
|
|
303
|
+
/** change-approval spec (github-app-recording-identity): a GitHub App credential that is incomplete, ambiguous, unreadable, or refused. */
|
|
304
|
+
export declare class ForgeCredentialsError extends ContextureError {
|
|
305
|
+
constructor(message: string, variables: readonly string[]);
|
|
306
|
+
}
|
|
283
307
|
//# sourceMappingURL=errors.d.ts.map
|