Skip to content

Add first-class forge issue and pull request tools - #471

Open
LeanAndMean wants to merge 14 commits into
mainfrom
feature/issue-468-forge-artifact-tools
Open

Add first-class forge issue and pull request tools#471
LeanAndMean wants to merge 14 commits into
mainfrom
feature/issue-468-forge-artifact-tools

Conversation

@LeanAndMean

Copy link
Copy Markdown
Owner

Summary

  • Add eight independently allowlistable tools for reading, creating, commenting on, and precisely editing current-repository issues and pull requests.
  • Support GitHub and GitLab through deterministic bounded XML, trusted prior-read evidence, serialized and verified mutations, safe CLI stdin transport, and actionable startup diagnostics.
  • Migrate covered Mach 12 forge operations to the new tools, remove superseded GitHub delegates, and document the public contract.

Test plan

  • Run focused coding-agent and forge tool, adapter, document, integration, and Mach 12 migration tests.
  • Run npm run typecheck.
  • Run npm run build.
  • Run npm test (2,648 tests).
  • Run npm run lint.

Fixes #468

@LeanAndMean

Copy link
Copy Markdown
Owner Author

Automated PR Review

Critical

No critical findings.

Important

F1: Tool-time CLI errors discard the diagnostics needed to recover. runForgeCommand() converts every nonzero result into a ForgeCommandError containing only command/argv and a stdin digest; it drops exit code, bounded stdout/stderr, and authentication classification (packages/scramjet/src/forge/client.ts:18-24, packages/scramjet/src/forge/client.ts:39-52, packages/scramjet/src/forge/client.ts:85-89). Consequently, an HTTP 401, invalid branch, permission denial, or API validation failure reaches the agent only as Command failed: "gh" ..., contrary to #468's actionable-error acceptance criterion. Preserve bounded, safely rendered process diagnostics without exposing stdin, and give tool-time authentication failures the same login guidance as the startup probe; add tests proving useful diagnostics survive while bodies remain redacted. Per mach12:silent-failure-hunter and mach12:feature-completeness-checker.

F2: A generic nonzero mutation exit is treated as proof that no write occurred. mutationAttemptFailure() returns every ForgeCommandError("failed") directly (packages/scramjet/src/forge/tools.ts:394-398), but a provider may accept a POST/PATCH before the CLI loses or fails to decode the response. The direct error omits the “may have succeeded” reconciliation warning, so a workflow can retry and create a duplicate issue, PR, or comment. Once a mutation process has started, classify failures as ambiguous unless retained provider evidence conclusively proves rejection; keep preflight and missing-executable failures definite and cover nonzero post-send failures in the mutation tests. Per mach12:code-reviewer and mach12:silent-failure-hunter.

F3: Valid lowercase GitHub origins break mutation identity verification. Mutation response URLs are compared byte-for-byte with URLs constructed from the origin's casing (packages/scramjet/src/forge/github.ts:660-675, packages/scramjet/src/forge/github.ts:679-703). GitHub repository paths are case-insensitive but responses use canonical casing; an authenticated read probe against repos/leanandmean/scramjet/issues/468 returned https://github.com/LeanAndMean/scramjet/issues/468. A clone using lowercase origin therefore performs a valid write and then reports an ambiguous malformed-response failure. Resolve canonical repository identity or compare parsed GitHub owner/repository segments case-insensitively, with artifact and comment mutation regressions. Per mach12:code-reviewer.

F4: Native GitHub relationship failures silently become authoritative empty results. readRelationships() catches every nonzero native GraphQL failure and falls back to task-list parsing (packages/scramjet/src/forge/github.ts:522-534), even for authentication, rate-limit, permission, network, or server failures. The returned issue still renders capability="supported"; the migrated commands explicitly interpret an empty section as no assignable sub-issues (packages/scramjet/mach12/commands/mach12:issue-plan.md:268, packages/scramjet/mach12/commands/mach12:issue-implement.md:104). Restrict fallback to a conclusively unsupported native capability, or represent degraded/incomplete evidence and retain the original diagnostic; never turn an operational failure into proof that no children exist. Per mach12:silent-failure-hunter.

F5: Cross-repository native sub-issues can assign an unrelated local issue. The relationship model preserves only number and URL, not repository identity (packages/scramjet/src/forge/types.ts:40-47), and GitHub parsing/deduplication keys native children by bare number (packages/scramjet/src/forge/github.ts:440-477). Both migrated commands then pass every child number to the current-repository-only mach12:gh-assign (packages/scramjet/mach12/commands/mach12:issue-plan.md:268-276, packages/scramjet/mach12/commands/mach12:issue-implement.md:104-112). A native child in another repository can therefore cause the same-numbered issue in this repository to be assigned. Preserve repository identity and only assign same-repository children; report external children without mutating them and add a cross-repository fixture. Per mach12:code-reviewer.

F6: GitLab commit and pipeline reads cannot prove pagination completeness. readList() flattens glab --paginate --output ndjson without page metadata (packages/scramjet/src/forge/gitlab.ts:201-210); files and comments have independent expected counts, but commits and checks only reject duplicate IDs (packages/scramjet/src/forge/gitlab.ts:534-549, packages/scramjet/src/forge/gitlab.ts:568-584). A valid partial NDJSON stream that exits zero is accepted as complete, violating the fail-closed requested-section contract and allowing reviews or release work to reason from missing commits/pipelines. Capture and validate pagination headers/page boundaries or compare against an authoritative count, then test premature successful termination. Per mach12:silent-failure-hunter.

F7: GitLab's unsupported review decision is indistinguishable from “no decision.” ForgePrReadiness.reviewDecision is string | null (packages/scramjet/src/forge/types.ts:59-65); GitLab always supplies null (packages/scramjet/src/forge/gitlab.ts:278-285), and the renderer omits null attributes (packages/scramjet/src/forge/document.ts:251-257). Mach 12 pre-merge consumes this document to require the PR to be free of requested changes, so absent capability can be mistaken for review-clear. Model and render supported-with-value versus unsupported/unknown explicitly, and require a narrow provider query where a workflow needs a determinate review decision. Per mach12:type-design-analyzer.

F8: GitLab draft creation changes the user-approved exact title. create_pr promises exact approved content, but an unprefixed title with draft: true is silently changed to Draft: <title> and that altered title is treated as the expected postimage (packages/scramjet/src/forge/tools.ts:332-345, packages/scramjet/src/forge/gitlab.ts:644-649, packages/scramjet/src/forge/gitlab.ts:701-713). Require the caller to approve/pass the provider-valid draft title, or clearly expose the normalized title before publication; do not silently mutate approved content. Update the public contract and tests accordingly. Per mach12:comment-analyzer.

F9: The review command's new “complete diff” contract still hardcodes main. The PR now requires every reviewer—and specifically the read-only completeness lens—to receive the complete diff, but computes it with origin/main...HEAD (packages/scramjet/mach12/commands/mach12:pr-review.md:54-58, packages/scramjet/mach12/commands/mach12:pr-review.md:91). For a PR targeting another base, reviewers receive the wrong patch even though read_pr later exposes the actual base. Read authoritative PR metadata first and diff against its fetched base branch; add a command-contract test for a non-main base. Per mach12:comment-analyzer.

F10: Successful edit tools discard the verified fresh postimage promised by the plan. The implementation refetches and byte-verifies the updated artifact/comment, then returns only identity, target, and requested deltas (packages/scramjet/src/forge/tools.ts:512-535, packages/scramjet/src/forge/tools.ts:598-604, packages/scramjet/src/forge/tools.ts:631-638). Stage 7 requires updated target content plus diff details, and omitting the postimage hides unrelated concurrent content that the protocol deliberately preserved. Return bounded verified fresh target content alongside the diff (without treating it as new read evidence), and pin it in issue, PR, and comment edit tests. Per mach12:feature-completeness-checker.

Suggestions

S1: Give expanded forge reads a human view, and separately reduce the agent-facing serialization noise. Ctrl+O currently appends the persisted result verbatim (packages/scramjet/src/forge/tools.ts:671-692), so users see every ]]>&#10;<!-- forge-break bridge generated for exact line-ending/chunk coverage (packages/scramjet/src/forge/document.ts:111-153). The agent receives that same canonical string; this is not only a TUI paint issue. Add a safe decoded/custom expanded renderer for humans without changing receipts, and separately evaluate a less token-heavy canonical syntax that retains exact CR/LF and coverage semantics for the model. Add a normal expanded-render regression—the current renderer test covers only collapsed output and malformed-details fallback (packages/scramjet/tests/forge-tools.test.ts:542-568).

S2: Make comment-title edits invalid in the provider schema, not only at execution. The edit schema permits {target:{kind:"comment"}, edits:[{field:"title", ...}]} and rejects it later (packages/scramjet/src/forge/tools.ts:112-130, packages/scramjet/src/forge/tools.ts:543-551). Reshape the Anthropic-compatible schema so artifact targets allow title/body while comment targets allow body only, preventing a deterministic wasted tool round. Per mach12:type-design-analyzer.

S3: Do not suppress unexpected startup-probe defects with the expected transient failures. The detached startup probe ends in .catch(() => {}) (packages/scramjet/src/forge/tools.ts:910-915), hiding resolver/programming/UI-notification errors as well as intentionally silent unsupported or transient outcomes. Represent expected silence as typed outcomes and log only unexpected exceptions, while preserving the non-blocking, no-noise startup contract. Per mach12:silent-failure-hunter.

S4: Remove the obsolete newline-terminated issue-body requirement. mach12:issue-create still requires the approved body to end with a newline (packages/scramjet/mach12/commands/mach12:issue-create.md:225), a constraint inherited from the deleted heredoc transport. create_issue sends exact JSON stdin and supports bodies without a final newline, so this gate needlessly changes or rejects otherwise exact approved content. Per mach12:comment-analyzer.

Strengths

  • Exact stdin transport uses argument arrays with shell: false, preserving arbitrary content without temporary files or process-list exposure.
  • Deterministic XML, snapshot continuation, field coverage, active-branch/post-compaction evidence, and same-assistant-batch rejection are unusually thorough and well aligned with the trust boundary.
  • Adapter parsing generally fails closed on malformed identities, duplicate data, requested facets, and post-write mismatches.
  • Existing-object queueing, refetch-before-write, exact original-snapshot replacements, and byte-level verification establish strong one-object mutation semantics.
  • The migration removes superseded delegates while retaining narrowly scoped CLI operations outside the eight-tool contract.
  • Reviewers verified typecheck, build, lint, all 2,648 workspace tests, and focused forge/runtime suites; no premature version or changelog change is present.

This is an automated review.

Reviewed by GPT-5.6 Sol

@LeanAndMean

LeanAndMean commented Aug 5, 2026

Copy link
Copy Markdown
Owner Author

Independent Review Assessment

Assessing review comment: #471 (comment)

Classifications

F1 — Genuine issue — Tool-time CLI errors discard recovery diagnostics. ForgeCommandError retains only invocation metadata while runForgeCommand() drops exit code/stdout/stderr (packages/scramjet/src/forge/client.ts:20-35, packages/scramjet/src/forge/client.ts:85-90), contrary to #468's actionable-error contract. Retain bounded diagnostics without stdin content, share authentication guidance, and test redaction.

F2 — Genuine issue — Generic nonzero mutation exits are treated as definite failure. mutationAttemptFailure() returns kind: "failed" directly after a write attempt (packages/scramjet/src/forge/tools.ts:394-398), and tests pin that unsafe behavior. Treat post-send failures as ambiguous unless structured diagnostics conclusively establish rejection; only pre-send and missing-executable failures are inherently definite.

F3 — Genuine issue — Lowercase GitHub origins break mutation identity verification. Response URLs are compared case-sensitively against repository.projectPath (packages/scramjet/src/forge/github.ts:660-703) although GitHub canonicalizes owner/repository casing. Strictly parse response URLs, compare repository segments case-insensitively, and preserve the canonical response URL.

F4 — Genuine issue — Native GitHub relationship failures become authoritative empty results. Every nonzero native relationship call falls back (packages/scramjet/src/forge/github.ts:522-534), so an operational failure with no task-list references becomes a supported empty set that Mach 12 treats as no children. Fall back only for a specifically recognized unsupported-capability response and propagate operational failures.

F5 — Genuine issue — Cross-repository native sub-issues can assign an unrelated local issue. Relationship data omits repository identity and keys children by number (packages/scramjet/src/forge/types.ts:40-47, packages/scramjet/src/forge/github.ts:440-477), while commands send every number to current-repository assignment. Preserve and validate repository identity, deduplicate by repository plus number, and assign only same-repository children.

F6 — Genuine issue — GitLab commit and pipeline reads cannot prove pagination completeness. readList() discards page metadata, while commits and checks validate only duplicate IDs (packages/scramjet/src/forge/gitlab.ts:201-210, packages/scramjet/src/forge/gitlab.ts:534-584). Capture authoritative totals/page progression or equivalent counts and reject successful partial streams.

F7 — Genuine issue — GitLab's unsupported review decision is indistinguishable from no decision. GitLab hardcodes null, the shared type conflates null with unsupported, and rendering omits it (packages/scramjet/src/forge/gitlab.ts:278-285, packages/scramjet/src/forge/types.ts:59-65, packages/scramjet/src/forge/document.ts:251-257). Model capability separately and require a supported provider query or an incomplete result where readiness needs a determinate decision.

F8 — Genuine issue — GitLab draft creation changes the approved exact title. Orchestration and adapter code prefix an unprefixed approved title (packages/scramjet/src/forge/tools.ts:332-349, packages/scramjet/src/forge/gitlab.ts:644-649). Require the approved title to carry a valid draft marker, then transport and verify it unchanged.

F9 — Genuine issue — The complete-diff contract hardcodes main. The review command diffs origin/main...HEAD before reading the actual PR base (packages/scramjet/mach12/commands/mach12:pr-review.md:52-61) yet supplies that as the complete reviewer diff. Read PR metadata first, fetch its base ref, and diff against that base, with a non-main-base regression test.

F10 — Genuine issue — Edit tools discard the verified fresh postimage. The refetched artifact/comment is verified but editMutationResult() receives only identity, target, and deltas (packages/scramjet/src/forge/tools.ts:512-638), contrary to #468 Stage 7. Return a bounded verified artifact title/body or comment body while keeping mutation details non-authorizing.

S1 — Genuine issue — Multiline forge reads can regress token efficiency and expose unreadable canonical bridges. A reproducible o200k_base benchmark on the same live data measured issue #468 at 21,404 tokens in the new XML versus 12,746 for the removed delegate prompt plus compact complete JSON—a 67.9% regression caused by 721 per-line bridge markers; removing only those bridges reduced the same XML to 11,670 tokens. Replace ordinary-LF bridge markup with a substantially leaner representation while preserving exact CR/LF semantics, line addressing, snapshots, lossless continuation, and trusted coverage; add representative efficiency evidence and a normal expanded-render regression.

S2 — Nitpick — Comment-title edits remain provider-schema-valid. The schema permits the combination, but execution rejects it before repository or adapter work and tests cover that boundary (packages/scramjet/src/forge/tools.ts:112-132, packages/scramjet/src/forge/tools.ts:548-551). Improve schema guidance or use a proven Anthropic-compatible conditional while preserving the runtime guard.

S3 — Nitpick — The startup probe suppresses unexpected defects with expected silent outcomes. The terminal catch hides all detached failures (packages/scramjet/src/forge/tools.ts:909-915), while expected unsupported, transient, and stale-UI cases intentionally remain silent. Distinguish expected outcomes and journal only unexpected exceptions without making startup blocking or noisy.

S4 — Genuine issue — The final-newline issue-body requirement is obsolete. create_issue accepts arbitrary exact bodies, but the migrated command and test still require a final newline (packages/scramjet/mach12/commands/mach12:issue-create.md:223-227, packages/scramjet/tests/mach12-wiring.test.ts:941-946). Remove that gate and update the contract test while retaining the one-line title rule.

Summary

  • Genuine issues: 12
  • Nitpicks: 2
  • False positives: 0
  • Deferred: 0
  • Regressions: 0

Staged Implementation Plan

  1. Required — Actionable process diagnostics (F1). Update forge/client.ts, focused tests, and docs/forge-tools.md to retain bounded safe diagnostics and shared authentication classification.
  2. Required — Truthful mutation outcomes (F2). Update mutationAttemptFailure() and mutation tests so post-send uncertainty is reconciled rather than reported as definite failure.
  3. Required — GitHub identity and relationships (F3, F4, F5). Add case-insensitive repository URL correlation, repository-qualified relationship data, operational-failure propagation, and same-repository assignment filtering across adapter/model/document/commands/fixtures/tests.
  4. Required — GitLab completeness and readiness (F6, F7, F8). Validate pagination completeness, model review-decision capability, and reject unapproved draft-title normalization across adapter/model/document/tools/docs/tests.
  5. Required — Verified edit postimages (F10). Return bounded verified target content with edit deltas and cover artifact/comment, concurrent preservation, and truncation cases.
  6. Required — Token-efficient, readable forge rendering (S1). Redesign ordinary multiline field encoding in forge/document.ts and forge/tools.ts so representative line-heavy artifacts are competitive with the removed delegate-plus-JSON path, while preserving exact decoded content, CR/LF distinctions, bounded ranges, snapshots, receipt coverage, and safe Ctrl+O rendering; update document/tool tests and the public contract.
  7. Required — Mach 12 command corrections (F9, S4). Derive the actual PR base before diffing, remove the final-newline body gate, and update wiring tests.
  8. Optional — Provider schema guidance (S2). Add an Anthropic-compatible conditional only if empirically accepted; otherwise strengthen descriptions while retaining runtime validation.
  9. Optional — Startup observability (S3). Convert expected probe failures to silent outcomes and log only unexpected defects through the existing logger, with tests and logging documentation updates.

No findings are deferred or classified as Regression.


Assessed by GPT-5.6 Sol

@LeanAndMean

Copy link
Copy Markdown
Owner Author

Review fixes

Originating review ID: 5187955499

Implemented the complete assessed repair batch:

  • Retained bounded, redacted process diagnostics with shared authentication guidance, while treating generic post-send mutation failures as ambiguous.
  • Made GitHub mutation identity casing-safe, restricted relationship fallback to conclusive native-capability absence, and preserved repository identity so external same-number children are never assigned locally.
  • Added authoritative GitLab commit/pipeline totals, explicit review-decision capability, and exact caller-approved draft-title transport.
  • Returned bounded verified fresh edit postimages without creating new read authority.
  • Replaced ordinary multiline bridge noise with lossless LF/CR-aware rendering and a control-safe persisted human expansion view.
  • Derived review diffs from the verified PR base, removed the obsolete issue-body newline gate, strengthened edit schema guidance, and journaled unexpected startup-probe defects.

Commit: bf7088ff5a0699d92a44266762b35c1f0ba27cfa (Make forge evidence and mutations fail safely)

Verification: npm run typecheck, npm run build, npm test (2,673 tests), and npm run lint all pass. Focused forge and Mach 12 coverage includes 291 passing tests plus an executable single-branch non-main-base fetch regression.

Notable decisions: provider-schema guidance remains flat to preserve the public edit call shape while runtime validation stays authoritative; GitLab review decisions remain explicitly unsupported rather than guessed; mutation diagnostics classify only redacted authentication evidence as conclusively rejected.

Reviewed by GPT-5.6 Sol

@LeanAndMean

Copy link
Copy Markdown
Owner Author

Automated Executable PR Validation

Executable validation of PR #471 against linked issue #468 retained two independently admitted new-contract defects. Both proofs are normalized in existing permanent suites and fail on the frozen PR head without setup, discovery, or environmental errors.

Important

F1 — Forge failure diagnostics expose terminal-control and bidirectional formatting characters

  • Severity: Important (merge-blocking)
  • Production references: packages/scramjet/src/forge/client.ts:69-98
  • Final test path: packages/scramjet/tests/forge-client.test.ts
  • Exact node ID: runForgeCommand > escapes terminal control characters in exposed diagnostics
  • Focused command (display-only): node ./node_modules/vitest/vitest.mjs run --testNamePattern "escapes terminal control characters in exposed diagnostics" packages/scramjet/tests/forge-client.test.ts
  • Ownership group: F1
  • Proof-patch SHA-256: 1250ec97ee2ec2af6523b4782609ae76b15619b598fd9b6594f693544e79ef65

Expected behavior: Bounded forge stdout/stderr remains actionable, but raw C1 and bidirectional formatting controls are rendered as visible escape sequences.

Observed behavior: messageForFailure() applies JSON.stringify() directly to stdout/stderr. It escapes C0 controls such as ESC but preserves raw U+009B and U+202E, so the focused assertion receives true for a raw-control search.

Head / merge-base classification: PR head red / merge base inapplicable. The forge client subsystem does not exist at merge base 2bff1f81d4cb3fca193c39a4c1c719fc13cf3b34; this is an approved new-contract defect, not a regression claim.

Root cause and confidence: High confidence. The production path is runForgeCommand() → bounded/redacted process diagnostics → messageForFailure(), whose two direct JSON.stringify() calls leave C1 and bidi controls untouched.

Approved-plan scope defense: Issue #468 requires safely rendered actionable command diagnostics, and the approved plan plus packages/scramjet/docs/forge-tools.md require bounded, control-safe stdout/stderr. The proof tests that exact trust boundary without broadening forge behavior.

Practical impact: A failing gh or glab invocation can carry provider/server text containing presentation controls. The trigger is uncommon but production-reachable. The observer-visible consequence is spoofed or reordered diagnostic text in the model/user transcript. The defect does not directly corrupt durable repository state, but it weakens the diagnostic trust boundary during mutation recovery. Operational severity is Important.

Minimal fix direction: Add a local diagnostic-display helper in forge/client.ts that preserves current JSON quoting/C0 escaping, then visibly escapes remaining C1 and bidi formatting controls (and the existing display-policy noncharacters) as uppercase four-digit \uXXXX. Use it only for rendered stdout/stderr. Preserve pre-render redaction, byte/line bounds, authentication classification on unrendered redacted text, and existing recovery guidance.

Exact proof patch:

diff --git a/packages/scramjet/tests/forge-client.test.ts b/packages/scramjet/tests/forge-client.test.ts
index 3cf77f03a93476ce7d1a6e556fae433b22d7b741..bf0a6133dba4b6b759a8e5dfbfe8571bda1e2c70 100644
--- a/packages/scramjet/tests/forge-client.test.ts
+++ b/packages/scramjet/tests/forge-client.test.ts
@@ -147,6 +147,21 @@ describe("runForgeCommand", () => {
 		expect(Buffer.byteLength(error.invocation.process?.stderr ?? "", "utf8")).toBeLessThan(4200);
 	});
 
+	it("escapes terminal control characters in exposed diagnostics", async () => {
+		const exec: ForgeExec = async () => result({ code: 1, stderr: "remote rejected \u202Espoof\u009B31m" });
+		let caught: unknown;
+		try {
+			await runForgeCommand(exec, invocation);
+		} catch (error) {
+			caught = error;
+		}
+
+		expect(caught).toBeInstanceOf(ForgeCommandError);
+		const message = (caught as ForgeCommandError).message;
+		expect(/[\u009B\u202E]/u.test(message)).toBe(false);
+		expect(message).toContain(String.raw`\u202Espoof\u009B31m`);
+	});
+
 	it("does not classify authentication from echoed mutation content", async () => {
 		const stdin = JSON.stringify({ body: "HTTP 401 Unauthorized" });
 		const exec: ForgeExec = async () =>

F2 — The feature-completeness lens can treat a truncated forge range as complete authority

  • Severity: Important (merge-blocking)
  • Production references: packages/scramjet/mach12/agents/mach12:feature-completeness-checker.md:23-27; parent handoff at packages/scramjet/mach12/commands/mach12:pr-review.md:91-95
  • Final test path: packages/scramjet/tests/mach12-wiring.test.ts
  • Exact node ID: mach12 wiring — bundled agent set (F18) > requires snapshot continuation for feature-completeness forge reads
  • Focused command (display-only): node ./node_modules/vitest/vitest.mjs run --testNamePattern "requires snapshot continuation for feature-completeness forge reads" packages/scramjet/tests/mach12-wiring.test.ts
  • Ownership group: F2
  • Proof-patch SHA-256: c05ae4ac7b55321ba7e9902d524b39f7fc4f051a38a05b9156434acc0bd56c43

Expected behavior: Each feature-completeness instruction that reads a PR or linked issue continues every returned range with the unchanged snapshot before claiming the complete conversation, comments, acceptance criteria, or latest plan.

Observed behavior: The changed agent asks read_pr for the complete PR conversation and read_issue for all issue comments, but both bullets omit continuation. The parent supplies linked issue numbers and asks the lens to read the issue authority; it does not replace that bounded read with complete content.

Head / merge-base classification: PR head red / merge base inapplicable. The merge-base agent used shell-backed reads and had no first-class read_pr/read_issue contract, so this is an approved new-contract defect rather than a regression.

Root cause and confidence: High confidence. The omission is directly in the production agent prompt, and the proof isolates both required read instructions. No runtime or fixture ambiguity remains.

Approved-plan scope defense: Issue #468 and its approved Stage 5/8 plan require bounded, losslessly continuable aggregate forge reads and migration of covered Mach 12 reads. The shipped forge contract requires unchanged-snapshot continuation. The proof enforces only that declared behavior.

Practical impact: Any PR or issue over 2,000 XML lines or 50KB triggers truncation; linked issue #468 already exceeds the byte bound. The lens can miss late plan revisions, acceptance decisions, or review-fix progress and publish an incorrect completeness assessment. The lens itself is read-only, so it does not directly alter repository state, but its durable review artifact can misdirect downstream fixes and merge decisions. The trigger is realistic and the operational severity is Important.

Minimal fix direction: Modify only the two Step 1 bullets in mach12:feature-completeness-checker.md so each explicitly continues every returned range with the unchanged snapshot. Preserve the read-only tool allowlist, complete-diff parent handoff, latest-plan semantics, and absence of bash.

Exact proof patch:

diff --git a/packages/scramjet/tests/mach12-wiring.test.ts b/packages/scramjet/tests/mach12-wiring.test.ts
index 33790ae05e355eab427937d9c20c03337a07e0d3..f96c1cb0632df24fa2e3f59aa484e86486e1f1b9 100644
--- a/packages/scramjet/tests/mach12-wiring.test.ts
+++ b/packages/scramjet/tests/mach12-wiring.test.ts
@@ -1408,6 +1408,19 @@ describe("mach12 wiring — bundled agent set (F18)", () => {
 		expect(tools).not.toContain("bash");
 		expect(content).toContain("Use the complete diff supplied by the parent review command");
 	});
+
+	it("requires snapshot continuation for feature-completeness forge reads", () => {
+		const content = readFileSync(join(MACH12_AGENTS_DIR, "mach12:feature-completeness-checker.md"), "utf-8");
+		const contextStep = content.slice(
+			content.indexOf("### Step 1: Gather Requirements Context"),
+			content.indexOf("### Step 2: Catalog the Actual Changes"),
+		);
+
+		for (const tool of ["read_pr", "read_issue"]) {
+			const instruction = contextStep.split("\n").find((line) => line.includes(`Use \`${tool}\``));
+			expect(instruction).toContain("continue every returned range with the unchanged snapshot");
+		}
+	});
 });
 
 describe("mach12 test designer contract", () => {

Candidate dispositions

Disposition Count
Retained independently validated findings 2
Removed passing tests 4
Removed invalid fixture or intended behavior 0
Removed duplicate or existing coverage 0
Removed pre-existing issue 0
Removed inconclusive or environmental observation 0
Rejected before implementation 0

Non-finding coverage observations:

  • Missing executable plus supplied stdin settled as one structured ENOENT spawn failure with no stdin error.
  • Mixed unsupported and rate-limited GitHub relationship errors propagated without task-list fallback.
  • A valid [Draft] GitLab prefix, doubled spacing, and Unicode title content were transported exactly.
  • A generic edit request failure was reported as ambiguous after one write attempt and no verification retry.

No narrowing allowance was consumed. No production boundary was left unreviewed: all 27 changed production/command files were assigned exactly once across six behavioral clusters. Test-only and documentation changes were retained as coverage and contract evidence.

Consolidated red result

Display-only command:

node ./node_modules/vitest/vitest.mjs run --no-file-parallelism --maxWorkers=1 --testNamePattern "escapes terminal control characters in exposed diagnostics|requires snapshot continuation for feature-completeness forge reads" packages/scramjet/tests/forge-client.test.ts packages/scramjet/tests/mach12-wiring.test.ts

Result: two test files failed with exactly the two retained nodes; 169 unrelated nodes were skipped. There were no extra assertion, setup, discovery, unhandled, or environmental failures.

Structured executable manifest

Rendered command strings above are display-only. A fresh session must reconstruct argv locally from this manifest.

{
  "cwd": ".",
  "runner": "node",
  "runnerArgvPrefix": ["./node_modules/vitest/vitest.mjs", "run"],
  "runnerAuthority": ["package.json devDependencies.vitest", "packages/scramjet/package.json scripts.test", "packages/scramjet/vitest.config.ts"],
  "nodes": [
    {
      "finding": "F1",
      "path": "packages/scramjet/tests/forge-client.test.ts",
      "nodeId": "runForgeCommand > escapes terminal control characters in exposed diagnostics",
      "testNamePattern": "escapes terminal control characters in exposed diagnostics"
    },
    {
      "finding": "F2",
      "path": "packages/scramjet/tests/mach12-wiring.test.ts",
      "nodeId": "mach12 wiring — bundled agent set (F18) > requires snapshot continuation for feature-completeness forge reads",
      "testNamePattern": "requires snapshot continuation for feature-completeness forge reads"
    }
  ],
  "consolidatedArgs": [
    "--no-file-parallelism",
    "--maxWorkers=1",
    "--testNamePattern",
    "escapes terminal control characters in exposed diagnostics|requires snapshot continuation for feature-completeness forge reads",
    "packages/scramjet/tests/forge-client.test.ts",
    "packages/scramjet/tests/mach12-wiring.test.ts"
  ],
  "validation": "Repository-relative paths were control-free, did not begin with an option prefix, and resolved inside the applicable worktree. Node IDs were locally authored control-free values."
}

Merge-base comparison requires a fresh detached worktree at the frozen actual merge base. F1 is base-inapplicable because packages/scramjet/src/forge/client.ts does not exist there. F2 is base-inapplicable because the merge-base feature-completeness agent uses shell-backed reads and has no first-class forge-read instruction.

Proof-patch manifest

Ownership group Findings Repository-relative patch path SHA-256
F1 F1 packages/scramjet/tests/forge-client.test.ts 1250ec97ee2ec2af6523b4782609ae76b15619b598fd9b6594f693544e79ef65
F2 F2 packages/scramjet/tests/mach12-wiring.test.ts c05ae4ac7b55321ba7e9902d524b39f7fc4f051a38a05b9156434acc0bd56c43

Exhaustive finding-to-ownership-group mapping: F1 → F1; F2 → F2. The groups share no support addition or overlapping hunk. Applying both distinct patches in manifest order reconstructs the complete normalized worktree diff byte-for-byte; normalized diff SHA-256: 3da1122917c6be21587691937876828391d10ec3e70a4cc8771f81134b07f0f3.

Frozen identities and publication guard

  • Repository: LeanAndMean/scramjet
  • PR: Add first-class forge issue and pull request tools #471
  • Publisher login: LeanAndMean
  • Local branch: feature/issue-468-forge-artifact-tools
  • Reviewed PR head: bf7088ff5a0699d92a44266762b35c1f0ba27cfa
  • Recorded base OID: 2bff1f81d4cb3fca193c39a4c1c719fc13cf3b34
  • Actual merge-base OID: 2bff1f81d4cb3fca193c39a4c1c719fc13cf3b34
  • Initial preflight: clean index and worktree; local branch/head exactly matched the open PR.
  • Publication guard: local and GitHub heads still match the frozen head; index is empty; only the two normalized test files are dirty; there is no production diff, untracked file, or temporary investigation file.

This is an automated executable review.

Reviewed by GPT-5.6 Sol

@LeanAndMean

Copy link
Copy Markdown
Owner Author

Executable validation repair

Committed and pushed 393c55e4a (fix: harden forge diagnostics and complete snapshot reads).

  • Forge failure diagnostics now visibly escape terminal-affecting C0/C1 and bidirectional controls across exposed invocation and process fields, preserve redaction and authentication classification, and enforce the final 4,096-byte/40-line process bounds after escape expansion.
  • The feature-completeness lens now continues every read_pr and read_issue range with the unchanged snapshot before treating authority as complete.
  • Both retained proof patches were committed unchanged and their focused nodes plus the exact consolidated invocation now pass.

Verification passed: 293 affected tests, all 2,675 workspace tests, npm run typecheck, npm run build, targeted Biome checks, and a disposable expansion/bounding probe. A fresh post-fix code review found no defects.

Recovery deviation: GitHub altered control-character evidence in the original validation publication, so its whole-comment authentication and the dependent remote assessment artifact were deliberately bypassed. Both findings were instead independently reassessed from current PR/issue authority, immutable Git evidence, and fresh executable reruns before repair. The underlying handoff defect is tracked in #472.

Reviewed by GPT-5.6 Sol

@LeanAndMean

Copy link
Copy Markdown
Owner Author

Automated PR Review

Critical

No critical findings.

Important

F1: Partial mutation-content echoes can be misclassified as conclusive authentication rejection. Diagnostic redaction records the complete stdin and complete decoded JSON string values, but not fragments of those values (packages/scramjet/src/forge/client.ts:122-151). Authentication classification then scans the remaining stdout/stderr (packages/scramjet/src/forge/client.ts:177-202), and mutationAttemptFailure() treats a matching failed command as definite rejection (packages/scramjet/src/forge/tools.ts:408-415). If a multiline issue/comment body contains an auth-looking line such as HTTP 401 Unauthorized and the CLI echoes only that line, it survives redaction, becomes authenticationFailure: true, and suppresses the required “may have succeeded” warning. A retry can duplicate a successful mutation. Suppress conclusive auth classification when the matched auth text also occurs anywhere in the original stdin, and add a fragment-echo regression. Per mach12:silent-failure-hunter.

F2: GitLab PR title edits can silently change draft state. Artifact edits compute and send any exact replacement title (packages/scramjet/src/forge/tools.ts:593-616, packages/scramjet/src/forge/gitlab.ts:760-775), but GitLab derives merge-request draft state from title prefixes such as Draft:. Adding or removing one therefore mutates draft state even though draft mutation is outside the documented edit surface (packages/scramjet/docs/forge-tools.md:124). Verification checks only title/body and does not compare readiness.draft (packages/scramjet/src/forge/tools.ts:625-630), so the extra mutation is reported as successful. Reject GitLab PR title edits that change prefix-derived draft state and verify draft state remains unchanged. Per mach12:code-reviewer.

F3: Deferred-finding issue publication lacks approval of the exact title and body. The assessment command asks the user only whether to create issues, then performs duplicate classification, constructs overlap notes, and instructs the agent to generate a summarizing title/body before calling create_issue (packages/scramjet/mach12/commands/mach12:pr-review-assessment.md:121-152, packages/scramjet/mach12/commands/mach12:pr-review-assessment.md:164-187). That can publish generated content the user never saw, conflicting with the new tool contract that create_issue is called only after exact title/body approval. Present each final body, including overlap notes, and exact title for explicit approval before publication; pin the ordering in the wiring tests. Per mach12:comment-analyzer.

F4: The explicit permission-separation acceptance criterion has no executable proof. The tools are independently registered and documented (packages/scramjet/src/forge/tools.ts:765-925, packages/scramjet/docs/forge-tools.md:16), but the real-session test registers the full custom tool set and never configures a read-only active-tool set (packages/scramjet/tests/forge-tools.test.ts:492-520). Thus no test proves the #468 requirement that an agent granted only read_issue/read_pr can read while mutation tools and bash remain unavailable. Add one integration test with an enforced read-only active-tool selection and assert both successful reading and absence of mutation/shell tools. Per mach12:feature-completeness-checker.

F5: Branch creation assumes an issue title is always in the first bounded range. mach12:issue-implement explicitly treats the title as present in the initial read_issue result before creating a branch (packages/scramjet/mach12/commands/mach12:issue-implement.md:77-80), but canonical labels, assignees, and relationships precede the title and output is capped at 2,000 lines/50KB (packages/scramjet/src/forge/document.ts:300-364, packages/scramjet/docs/forge-tools.md:53-57). A relationship-heavy issue can therefore truncate before the title. Continue with the unchanged snapshot until the title is visible, or perform the already-required complete read before branch-name derivation. Per mach12:comment-analyzer.

Suggestions

S1: Make complete-conversation reads explicit in readiness commands. mach12:pr-pre-merge and mach12:pr-merge request a complete top-level conversation but do not explicitly require continuation (packages/scramjet/mach12/commands/mach12:pr-pre-merge.md:41-43, packages/scramjet/mach12/commands/mach12:pr-merge.md:31-35). Add the same unchanged-snapshot continuation wording used elsewhere so late comments are not mistaken for absent evidence.

S2: Add negative comment-evidence and process-settlement regressions. Cover missing executable plus supplied stdin in packages/coding-agent/tests/exec.test.ts, and cover incomplete/mixed-snapshot parent evidence plus timeout/cancellation/stdin ambiguity in packages/scramjet/tests/forge-tools.test.ts. These paths appear conservatively implemented but are important trust-boundary contracts currently inferred from adjacent tests. Per mach12:test-analyzer.

S3: Validate supported GitHub review decisions as a closed set. ForgePrReviewDecision allows any string and the GitHub adapter lowercases any provider value (packages/scramjet/src/forge/types.ts:60, packages/scramjet/src/forge/github.ts:431-436). Model known decisions explicitly and fail closed—or render an explicit unknown branch—rather than treating a future or malformed value as authoritative readiness evidence. Per mach12:type-design-analyzer.

S4: Remove duplicate canonical document storage. RenderedForgeDocument.text duplicates lines.join("\n"), while production consumers use lines and only tests use text (packages/scramjet/src/forge/document.ts:27, packages/scramjet/src/forge/document.ts:445). Keep the joined value local for hashing and have tests join lines when needed. Per mach12:code-simplifier.

Strengths

  • Shell-free argument-array execution and exact stdin transport preserve arbitrary content without temporary files or process-list exposure.
  • Deterministic bounded XML, snapshot continuation, typed coverage receipts, active-branch/post-compaction evidence, and same-assistant-batch rejection form a strong read-before-mutation boundary.
  • Provider adapters broadly fail closed on malformed identity, pagination, duplicate, and requested-facet evidence.
  • Queue-time refetch, one-write mutation semantics, exact postimage verification, and conservative generic ambiguity handling substantially reduce data-loss and duplicate-write risk.
  • The migration removes superseded delegates while retaining narrow shell operations outside the eight-tool contract; no premature version or changelog change is present.
  • Review lenses reported typecheck, build, lint, focused suites, and all 2,675 workspace tests passing at current head 393c55e4.

This is an automated review.

Reviewed by GPT-5.6 Sol

@LeanAndMean

Copy link
Copy Markdown
Owner Author

Independent Review Assessment

Assessing review comment: #471 (comment)

Classifications

F1 — Genuine issue — Partial mutation-content echoes can be misclassified as authentication rejection. Redaction removes complete stdin/string values only (packages/scramjet/src/forge/client.ts:116-151), while authentication detection scans the remaining output (packages/scramjet/src/forge/client.ts:171-196) and makes the mutation failure conclusive (packages/scramjet/src/forge/tools.ts:408-415). Excluding authentication matches also present in the original stdin is a contained conservative fix that preserves ambiguity and prevents unsafe retries.

F2 — Genuine issue — GitLab PR title edits can silently change draft state. GitLab sends edited PR titles directly (packages/scramjet/src/forge/gitlab.ts:762-775), while editArtifact verifies only title/body (packages/scramjet/src/forge/tools.ts:595-630), despite draft mutation being excluded from the tool surface (packages/scramjet/docs/forge-tools.md:122-124). Preflight rejection of prefix-induced draft changes plus post-write draft verification is safe and consistent with existing creation validation.

F3 — Genuine issue — Deferred-finding issues can be published without exact-content approval. The batch choice authorizes issue creation generally, but final titles, bodies, and overlap notes are assembled afterward and passed directly to create_issue (packages/scramjet/mach12/commands/mach12:pr-review-assessment.md:121-152, packages/scramjet/mach12/commands/mach12:pr-review-assessment.md:164-187). Requiring explicit approval of each exact final title/body immediately before publication safely restores the public tool contract.

F4 — Genuine issue — Permission separation lacks an executable integration proof. The real-session test registers all forge tools and invokes a read without narrowing the active set (packages/scramjet/tests/forge-tools.test.ts:492-520), although the runtime provides enforced active-tool selection (packages/coding-agent/src/core/agent-session.ts:950-994). A read-only session test asserting successful reads and the absence of mutation and shell tools is a contained proof of independent allowlisting.

F5 — Genuine issue — Branch creation assumes the issue title is in the initial bounded range. The branch flow relies on the initial result (packages/scramjet/mach12/commands/mach12:issue-implement.md:77-81), but relationships precede the title (packages/scramjet/src/forge/document.ts:321-365) and each result is bounded (packages/scramjet/docs/forge-tools.md:53-57). Moving the already-required complete snapshot read at mach12:issue-implement.md:102-104 before branch derivation safely removes the assumption.

S1 — Nitpick — Readiness commands should repeat continuation requirements. Both commands request the complete conversation (packages/scramjet/mach12/commands/mach12:pr-pre-merge.md:41-43, packages/scramjet/mach12/commands/mach12:pr-merge.md:31-35), and forge tool guidance already mandates continuation (packages/scramjet/src/forge/tools.ts:783-790). Repeating unchanged-snapshot wording locally is nevertheless a safe clarity improvement for high-impact merge gates.

S2 — Nitpick — Additional negative evidence and settlement regressions would strengthen coverage. Existing tests cover missing executables, stdin errors, timeout/abort settlement (packages/coding-agent/tests/exec.test.ts:26-87) and mixed-snapshot edit evidence (packages/scramjet/tests/forge-tools.test.ts:1197-1233), but not every proposed combination or mixed-snapshot parent-comment evidence (packages/scramjet/tests/forge-tools.test.ts:696-746). The focused additions are safe optional strengthening rather than evidence of a current implementation defect.

S3 — Genuine issue — Supported GitHub review decisions accept arbitrary strings. ForgePrReviewDecision accepts any string (packages/scramjet/src/forge/types.ts:60), and GitHub marks any returned string as supported readiness evidence after lowercasing (packages/scramjet/src/forge/github.ts:428-436). Restricting values to known GitHub decisions and rejecting unknown provider values is a contained fail-closed trust-boundary improvement.

S4 — Nitpick — Canonical document text is stored twice. RenderedForgeDocument.text duplicates lines.join("\n") (packages/scramjet/src/forge/document.ts:26-35, packages/scramjet/src/forge/document.ts:445-450) and production slicing consumes lines; current direct text consumers are tests. Removing the duplicate after hashing and joining in tests is a safe optional memory and representation simplification.

Summary

  • Genuine issues: 6
  • Nitpicks: 3
  • False positives: 0
  • Deferred: 0
  • Regressions: 0

Staged Implementation Plan

Stage 1 — Required: Harden forge mutation and readiness semantics

  • F1: Prevent stdin-derived authentication fragments from producing conclusive mutation rejection; add client and mutation-ambiguity regressions.
  • F2: Reject GitLab PR title edits that alter prefix-derived draft state, verify draft preservation, and add tool/adapter tests.
  • S3: Model GitHub review decisions as a closed set, fail closed on unknown values, and add adapter/document tests.
  • Files: packages/scramjet/src/forge/client.ts, tools.ts, gitlab.ts, github.ts, types.ts, and focused forge tests.

Stage 2 — Required: Restore exact deferred-issue publication approval

  • F3: Update mach12:pr-review-assessment.md so each finalized title/body, including overlap notes, is shown and explicitly approved immediately before creation.
  • Add mach12-wiring.test.ts coverage for final-content construction, approval, and publication ordering.

Stage 3 — Required: Make issue-title acquisition continuation-safe

  • F5: Move or reuse the complete unchanged-snapshot read_issue before branch-name derivation in mach12:issue-implement.md.
  • Add wiring coverage preventing restoration of the initial-range assumption.

Stage 4 — Required: Prove permission separation

  • F4: Add a real AgentSession integration test with only read_issue and read_pr active; assert reading succeeds and mutation tools plus bash are unavailable.

Stage 5 — Optional: Strengthen prompts and negative-path coverage

  • S1: Add explicit unchanged-snapshot continuation wording and wiring assertions to both readiness commands.
  • S2: Add missing-executable-with-stdin, parent-evidence, and mutation-ambiguity test combinations.

Stage 6 — Optional: Remove redundant rendered-document storage

  • S4: Remove RenderedForgeDocument.text, retain a local joined string for hashing, and update tests to join lines.

Assessed by GPT-5.6 Sol

@LeanAndMean

Copy link
Copy Markdown
Owner Author

Review fixes

Originating review ID: 5193276643

Implemented the complete assessed batch:

  • Prevented partial mutation-content authentication echoes from proving rejection, preserving ambiguous-write recovery.
  • Required GitLab PR title edits to preserve Draft/WIP state before mutation and verified draft state after mutation.
  • Restored explicit approval of each exact deferred-issue title/body, including overlap notes, immediately before creation.
  • Added a real AgentSession read-only tool-selection proof showing reads remain available while mutation tools and bash are removed.
  • Made issue-title branch derivation wait for complete same-snapshot issue evidence, and made both merge-readiness commands explicit about complete conversation continuation.
  • Added negative process/evidence regressions, closed GitHub review decisions to known values, and removed duplicate rendered-document text storage.

Commit: 9f71b3da73ee6ddacd85c05877a57e04379efa36 (Close remaining forge trust-boundary gaps)

Verification: npm run typecheck, npm run build, npm test (2,690 tests), and npm run lint all pass; 315 affected tests also pass. The focused quality pass additionally caught and closed legacy GitLab WIP prefix handling and strengthened the permission/command-contract proofs.

Reviewed by GPT-5.6 Sol

@LeanAndMean

Copy link
Copy Markdown
Owner Author

Forge Token and Recovery Optimization Plan

Goal and constraints

Reduce recurring and per-call model tokens while preserving the existing eight tool names and parameter schemas, permission separation, private read receipts, exact-edit behavior, one-write mutation protocol, and post-write verification.

Failures must remain concise, diagnosable, and recoverable through deliberate read-only gh/glab use when bash is available. The tools never invoke a shell fallback, and an ambiguous mutation must never invite a retry or CLI mutation substitute.

Primary token contract

Token efficiency is measured against the most compact practical, semantically equivalent provider-CLI result that the model would otherwise need to read:

  • Compare identical complete data, including required pagination, top-level comments, readiness facts, relationships, and requested optional PR facets.
  • Use compact gh/glab JSON or JSON-lines output, excluding shell-command prose but including all model-visible result text required to perform the same task safely.
  • A forge-tool read must match or beat that equivalent result under o200k_base; target at least 10% fewer tokens where the deterministic representation permits it.
  • A successful mutation result must match or beat a provider command filtered to canonical identity/URL only.
  • Failure output has a separate budget because actionable recovery may justifiably require more text.
  • Tool-definition and prompt-guideline cost is measured separately as recurring per-turn overhead.

CI uses deterministic UTF-8 byte ceilings as secondary regression guards. Token comparisons use disposable uv run --with tiktoken probes; no tokenizer dependency or configuration is added to the repository.

Public behavior

Successful mutations return only a compact verified identity summary, for example:

Created and verified issue #41: <canonical URL>
Edited and verified issue #7 fields=title,body replacements=4: <canonical URL>

They no longer repeat submitted bodies, aggregate XML, edit deltas, or verified postimages. The exact request remains in the persisted tool call, and full verification still happens internally.

Expected failures use four stable recovery classes:

Class Write certainty Recovery
FORGE_READ_FAILED No write attempted Retry the read or use deliberate read-only CLI inspection.
FORGE_PREFLIGHT_FAILED No write attempted Correct repository, evidence, input, or provider constraints.
FORGE_WRITE_REJECTED Conclusive no-write evidence Correct the prerequisite, then make a new deliberate tool call.
FORGE_WRITE_AMBIGUOUS Mutation may have succeeded Never retry; reread or inspect read-only before deciding.

Text-based authentication matching may provide login guidance, but it does not prove rejection after mutation dispatch. FORGE_WRITE_REJECTED is limited to evidence conclusively showing that no request reached the provider, such as a missing executable.

Stage 1 — Preserve structured details on returned tool errors

Modify:

  • packages/agent/src/types.ts
  • packages/agent/src/agent-loop.ts
  • packages/agent/tests/harness-tool-call.test.ts
  • packages/coding-agent/docs/extensions.md
  • UPSTREAM_DIVERGENCE.md

Add optional isError?: boolean to final AgentToolResult. executePreparedToolCall() initializes execution error state from result.isError ?? false; thrown-error behavior and afterToolCall overrides remain unchanged. Structured details persist into the final ToolResultMessage but remain outside provider-visible content.

Test first: returned errors emit and persist isError: true with exact details, omitted isError remains successful, and thrown errors retain current behavior.

Stage 2 — Remove redundant successful-call tokens

Modify:

  • packages/scramjet/src/forge/tools.ts
  • packages/scramjet/src/forge/document.ts
  • packages/scramjet/tests/forge-tools.test.ts
  • packages/scramjet/tests/forge-document.test.ts

Replace mutationResult() and editMutationResult() with one concise verified-result helper. Preserve operation, repository, identity, target, changed field names, replacement count, and verified: true; remove bodies, XML, deltas, and postimages from content and details. Delete renderForgeTargetPostimage() and related unused helpers.

Shorten descriptions, snippets, guidelines, and schema descriptions without changing schema shape, constraints, or requiredness. Preserve continuation, exact approval, prior-read, exact-edit, and no-Git-state guidance.

Test first: all six mutation tools still perform one write plus verification; large body/replacement sentinels appear nowhere in returned content/details; fixed mutation results remain within 512 UTF-8 bytes and match or beat filtered CLI identity output; all eight schemas reject public proof/receipt fields.

Stage 3 — Add classified recovery diagnostics

Modify:

  • packages/scramjet/src/forge/types.ts
  • packages/scramjet/src/forge/client.ts
  • packages/scramjet/src/forge/tools.ts
  • packages/scramjet/tests/forge-client.test.ts
  • packages/scramjet/tests/forge-tools.test.ts

Add model-hidden scramjet:forge-failure@1 details carrying failure class, operation, phase (repository, evidence, refetch, dispatch, response, or verify), write state (not_attempted, rejected, or possible), repository/target when known, and a bounded safe diagnostic.

Use one internal failure marker rather than a generalized result framework. Expected failures return isError: true. Read failures state that no mutation occurred and permit deliberate read-only gh/glab fallback only when bash is available. Mutation failures never suggest a CLI mutation substitute. Ambiguous mutations explicitly prohibit retry and require a fresh read or read-only inspection.

For invocations without stdin, retain bounded, redacted, control-safe stdout/stderr in details. For stdin-bearing mutations, retain only exit status, stream byte counts/hashes, stdin byte count/hash, process flags, and authentication guidance—never raw process output. Omit or fingerprint large GraphQL query arguments in model-visible text.

Test first with tables covering all classes/phases, zero-write preflight behavior, at-most-one-write ambiguity, forbidden retry language, correct GitHub/GitLab read-only fallback guidance, no automatic fallback invocation, secret/control redaction, bounded output, and structured details surviving a real AgentSession.

Stage 4 — Handle unknown provider values without favorable guessing

Modify:

  • packages/scramjet/src/forge/types.ts
  • packages/scramjet/src/forge/github.ts
  • packages/scramjet/src/forge/gitlab.ts
  • packages/scramjet/src/forge/document.ts
  • Provider/document tests

Represent future actor and review-decision values explicitly as unknown; never as approved, clear, or absent. Map unfamiliar mergeability and check states to explicit unknown rather than favorable states. Unknown GitLab changes_count must not block a core PR read when files were not requested, but requested files still fail when completeness cannot be proven. Preserve non-system, unpositioned GitLab notes rather than silently omitting a future note type.

Identity, ownership, pagination, requested-section completeness, evidence, and mutation-response validation remain strict.

Stage 5 — Gate representation compaction on CLI-equivalent measurements

Measure fixed issue/PR corpora against semantically equivalent compact gh and glab results before changing XML. Include line-heavy conversations and a metadata-heavy PR with 100 files, 100 commits, and 100 checks.

Adopt optional PR record compaction only if the candidate:

  • matches or beats equivalent CLI output under o200k_base;
  • saves at least 20% UTF-8 bytes and 15% tokens versus current optional-section XML;
  • does not regress core issue/PR conversation parity;
  • preserves every value, ordering, escaping, snapshot, continuation, and coverage invariant.

The candidate may use one-line read-only <file>, <commit>, and <check> records with deterministic expanded fallback for oversized values. If the gate fails, stop without changing XML.

Stage 6 — Documentation and verification

Update:

  • packages/scramjet/docs/forge-tools.md
  • packages/scramjet/docs/command-authoring.md
  • packages/scramjet/README.md
  • CLAUDE.md

Document concise mutation results, classified recovery, model-hidden diagnostics, read-only fallback rules, ambiguous-write handling, unknown safety values, and any optional XML change that passes Stage 5.

Run focused agent/forge suites, npm run typecheck, npm run build, npm test, and npm run lint.

Guardrails

  • No public receipt, proof, authority, diagnostic, or recovery handles.
  • No ninth tool, output-mode schema, dependency, or token-budget configuration.
  • No automatic bash, gh, or glab fallback.
  • No CLI mutation fallback after a forge mutation failure.
  • No retries, rollback, deduplication search, or cross-process locking.
  • No replacement of core XML with JSON or a custom record grammar.
  • No raw mutation body in argv, result text, diagnostics, logs, or issue bundles.
  • No weakening of identity, pagination, evidence, queue, or verification boundaries.
  • Model-hidden details never establish new read authority.

This plan intentionally uses deletion first. The only runtime addition is an optional error bit on the existing result object so the existing details channel can retain truthful diagnostics; it rejects exhaustive adapter outcome unions, branded completeness types, provider frameworks, and agent-managed recovery state.

Reviewed by GPT-5.6 Sol

Add an optional AgentToolResult.isError to the Pi agent runtime so
extension tools can return real error results while preserving their
structured details, instead of the previous throw-only path that
discarded them. Rewire the eight forge tools onto it: expected
post-validation failures return classified scramjet:forge-failure@1
details (class/phase/write-certainty) with model-hidden diagnostics,
distinguishing no-write paths from ambiguous writes that must never be
retried.

Reduce forge token cost to match or beat equivalent gh/glab JSON:
mutation success returns only the canonical URL; reads use compact
self-closing XML with a lossless expanded fallback; verbose schema and
prompt prose removed. Normalize unexpected provider states to explicit
non-favorable "unknown" values rather than throwing.

Records a new SCRAMJET-DIVERGENCE (agent-loop.ts, types.ts) in
UPSTREAM_DIVERGENCE.md and updates the affected docs.

Refs #468, #471.
@LeanAndMean

Copy link
Copy Markdown
Owner Author

Progress: forge token efficiency + graceful failure

Committed and pushed edc84d3a (Make forge tools token-efficient and fail gracefully), implementing the Forge Token and Recovery Optimization Plan (#471 (comment)). The work was authored in a prior session and had been left uncommitted on the branch; this batch commits and pushes it unchanged.

Summary of changes (by plan stage)

  • Stage 1 — runtime primitive: added optional AgentToolResult.isError to the Pi agent runtime (agent-loop.ts honors result.isError ?? false; thrown-error behavior preserved), so extension tools can return real error results while retaining structured details. Recorded as a new SCRAMJET-DIVERGENCE in UPSTREAM_DIVERGENCE.md.
  • Stage 2 — concise mutation results: successful mutations now return only the canonical URL; submitted bodies, aggregate XML, edit deltas, and verified postimages removed from result content/details.
  • Stage 3 — classified recovery diagnostics: expected failures return model-hidden scramjet:forge-failure@1 details (class / phase / write-state), distinguishing no-write paths from ambiguous writes that must never be retried; stdin-bearing mutations retain only byte counts/hashes, never raw process output.
  • Stage 4 — unknown provider values: unexpected actor / review-decision / mergeability / check states normalize to explicit non-favorable "unknown" rather than throwing; identity, pagination, and requested-section completeness stay strict.
  • Stage 5 — representation compaction: compact self-closing XML with lossless expanded fallback; representative reads now measure at or below semantically equivalent compact gh/glab JSON under o200k_base.
  • Stage 6 — docs: updated forge-tools.md, command-authoring.md, README.md, CLAUDE.md, and extensions.md.

Files: 20 changed (+1146 / −423) — agent runtime (3), forge production (6), forge tests (5), docs (6).

Verification

npm run typecheck, npm run lint, and npm test (all workspaces) pass at edc84d3a.

Notable decisions

  • The runtime isError bit is the minimal enabling change: tools must opt in, and throwing still works unchanged.
  • No automatic bash / gh / glab fallback, and ambiguous writes are never retried.
  • Decision-bearing unknown provider states never read as approved, mergeable, or success.

Committed by Claude Opus 4.8

…ge-artifact-tools

# Conflicts:
#	CLAUDE.md
#	packages/scramjet/mach12/commands/mach12:gh-comment.md
#	packages/scramjet/mach12/commands/mach12:pr-validation-assessment.md
#	packages/scramjet/mach12/commands/mach12:pr-validation.md
#	packages/scramjet/mach12/commands/mach12:push.md
#	packages/scramjet/tests/mach12-pr-validation.test.ts
@LeanAndMean

Copy link
Copy Markdown
Owner Author

Forge model/TUI representation alignment

Committed and pushed 1e8e3ee4 (Align forge output with its TUI rendering).

  • Replaced the separate agent transport XML and persisted Ctrl+O projection with one deterministic tagged-text representation; expanded TUI output now renders the exact model-visible result with only terminal styling/layout differences.
  • Removed CDATA, forge-break comments, continuation arrows, bespoke code-unit tags, and the duplicate details.display payload.
  • Kept untrusted tags and reserved escape sequences losslessly encoded while leaving ordinary Markdown punctuation readable and token-efficient, including escape sequences split across internal chunk boundaries.
  • Preserved snapshot/coverage evidence and lossless continuation, renamed range units to positions, and made every complete result—including continuation guidance—fit the 50KB output ceiling.
  • Simplified redundant wrapper metadata and absent collections; updated the public forge and command-authoring contracts.

TDD regressions cover model/TUI byte-for-byte parity, implementation-marker absence, reserved-escape chunk boundaries, control/code-unit rendering, full-range reconstruction, total result bounds, and Markdown-heavy efficiency. A disposable o200k_base probe measured representative tagged output at 12,101 tokens versus 12,102 for semantically equivalent compact JSON.

Verification passed: npm run typecheck, npm run build, npm test (2,698 tests), and npm run lint.

Reviewed by GPT-5.6 Sol

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add file-like tools for forge issues and pull requests

1 participant