Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## Unreleased

### Improved

- Rank focused implementation and test files ahead of broad historical acceptance harnesses for
ordinary task briefs.
- End initialization with an accurate created, updated, and reused setup summary plus the next local
action.
- Distinguish learning that was not assessed from an approved assessment with no reusable result.
- Expose prepared independent-review packages through the read-only `review` command.

## 0.1.1 — 2026-09-04

This patch release strengthens the existing Noxroot workflow without adding a new command or
Expand Down
23 changes: 9 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,9 @@ Commit the reviewed setup before your first code-changing task; `start` requires
baseline. This is a local commit; no push is needed. Still evaluating? `preview` and `context` work
without initialization or a setup commit.

Then keep talking to your coding agent normally. For code changes, compatible agents are instructed
to run the pinned `start` before editing and `finish` afterward.

Finish all edits, run `finish`, address any failures or required review, then commit. Rerun `finish`
if you edit again. Approve real project commands during setup; see the
[first-task guide](docs/getting-started.md) for missing checks, timeouts, and package-age
restrictions.
Then keep talking to your coding agent normally; compatible agents are instructed to record the
baseline before a code change, run the approved checks afterward, and surface any required review.
See the [first-task guide](docs/getting-started.md) when you want to inspect that flow yourself.

`init` preserves existing documentation and pins the Noxroot version. `npx` downloads from
[npm](https://www.npmjs.com/package/noxroot) into its cache; no global installation or clone is
Expand All @@ -98,10 +94,8 @@ ownership of lifecycle, review, and learning; Noxroot can supply context and ver
it. A coordination ledger is adjacent, not a development coordinator. Noxroot does not import its
log.

Read-only work creates no task. In the same repository, branch, and worktree, a repeated `start`
continues the active baseline. `finish` infers a single matching task; several matches require
`--task <id>`. Commands remain available for manual use when an agent does not follow the
instructions.
Read-only work creates no task. A repeated `start` continues the same task on the same branch and
worktree. Commands remain available for manual use when an agent does not follow the instructions.

When upgrading, inspect the managed instruction changes with
`npx noxroot@latest sync --dry-run --diff`. Apply them with `npx noxroot@latest sync` after review.
Expand All @@ -127,7 +121,8 @@ Existing `.git/noxroot` records stay in place, without a second store. If an age
state, it must stop and request access before continuing.

`SKILL.md` files are portable instructions for verification and review. `AGENTS.md`, the knowledge
index, and routes guide context loading. `finish` and `learn` handle learning proposals.
index, and routes guide context loading. `review` exposes the prepared package when a fresh review
is required. Only an approved review of the unchanged diff can produce a learning proposal.

Skills are instructions, not test evidence. Incomplete work cannot become approved. Noxroot does not
push, merge, publish, or deploy.
Expand Down Expand Up @@ -210,8 +205,8 @@ Python, Go, Rust, and other stacks. CI covers Windows, macOS, and Linux.
`context` explains file selection. `verify --plan` shows approved commands without running them.
`verify --changed` runs applicable checks. `run --dry-run` shows a connected execution plan.
`status` shows active work and the next action. `doctor` explains configuration problems.
`learn --task ID` shows confirmable durable proposals. Data commands support `--json`, with progress
and diagnostics on standard error.
`review --task ID` prepares a fresh-review handoff. `learn --task ID` shows confirmable durable
proposals. Data commands support `--json`, with progress and diagnostics on standard error.

Read [Getting started](docs/getting-started.md), the [command reference](docs/commands.md),
[configuration](docs/configuration.md), [architecture](docs/architecture.md),
Expand Down
16 changes: 16 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,18 @@ tracked, oversized, mismatched, and malformed evidence is rejected; rejected con
in task state. If a check or reviewer changes the repository, prior verification and review evidence
becomes stale and `finish` must be run again.

## `review`

`review [--task ID]` exposes the reviewer package already prepared by `finish` for a
`review-pending` task. Human output explains what review is required, why, and how to continue.
`--json` emits the complete bounded package for a fresh coding-agent reviewer, including the task,
change ID, changed paths, diff, verification evidence, review reasons, and strict response contract.
The command is read-only and does not invoke a reviewer.

Save the reviewer's one-object JSON response as an untracked file under `.noxroot/local/`, then pass
that relative path to `finish --review-file`. Noxroot validates the task and full-change IDs before
accepting the decision. Editing the repository makes the earlier package and response stale.

## `learn`

`learn --task ID` accepts structured reviewer candidates of kind `knowledge`, `decision`,
Expand All @@ -186,3 +198,7 @@ sessions, user data, secrets, and external human docs are not converted into kno
carry a confirmation date and source task id. Noxroot refuses another entry when the destination
would exceed `context.documentWarningBytes`; existing knowledge must then be consolidated or
superseded deliberately.

Human output distinguishes `not-assessed` from `no-candidate`. The former means there is no approved
review of the current unchanged diff; the latter means that review ran and identified no reusable
project knowledge. Neither state changes project memory.
11 changes: 8 additions & 3 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,14 @@ The handoff should name the changed files, commands that ran, failures, and anyt
Passing checks do not satisfy a required review. A task with incomplete verification is not
approved.

Useful lessons can be proposed for documentation after the task. Review those proposals before
applying them. No learning candidate is a valid outcome; every change does not need another
document.
If finish reports `review-pending`, run `noxroot review --task ID`. Give its JSON package to a fresh
coding-agent reviewer, save the strict response under `.noxroot/local/`, and pass that file back to
`finish --review-file`. Noxroot checks that the response belongs to the current unchanged diff.

Useful lessons can be proposed for documentation only after an approved review of the current
unchanged diff. Review those proposals before applying them. `Not assessed` means that review did
not happen; `no candidate` means it did and found nothing reusable. Every change does not need
another document.

An accepted lesson must also be eligible for the task's context route. Fresh setups include
`.noxroot/knowledge/**`; relevance and size limits still apply. If an older setup includes only
Expand Down
46 changes: 33 additions & 13 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ import { orchestrateRun, type RunRecord } from "./orchestration/run.js";
import {
renderContext,
renderGuidedFinish,
renderInitApplied,
renderInitMark,
renderLearning,
renderPreview,
renderReviewHandoff,
renderVerification,
renderVerificationPlan,
renderWelcome,
Expand Down Expand Up @@ -568,10 +570,13 @@ export function createProgram(customIo?: Partial<Io>): Command {
io.stderr("Noxroot is already initialized; no files were proposed.\n");
return;
}
const writableChanges = preview.proposedFiles.filter(
(proposal) => proposal.action !== "reference",
).length;
if (
!(await confirm(
io,
`Create exactly ${preview.proposedFiles.length} proposed file(s)?`,
`Apply exactly ${writableChanges} displayed setup change(s)?`,
options.yes,
))
) {
Expand All @@ -583,10 +588,7 @@ export function createProgram(customIo?: Partial<Io>): Command {
}
const result = await applyProposals(preview);
if (common.json) writeJson(io, { preview, applied: result });
else
io.stdout(
`Created ${result.created.length} file(s):\n${result.created.map((file) => `- ${file}`).join("\n")}\n`,
);
else io.stdout(renderInitApplied(result, renderOptions(io, common)));
},
);

Expand Down Expand Up @@ -684,6 +686,30 @@ export function createProgram(customIo?: Partial<Io>): Command {
emit(io, common.json, result, renderTaskStatus(result));
});

program
.command("review")
.description("show the prepared package for a required independent review")
.option("--task <task-id>", "guided task id; inferred when exactly one task is active")
.action(async (options: { task?: string }, command: Command) => {
const common = globals(command);
const root = path.resolve(common.root);
const config = await loadConfig(root);
if (refuseDisabledModule(io, common.json, config, "orchestration")) return;
const taskId = await inferGuidedTaskId(root, options.task);
const record = await readRunRecord<GuidedRunRecord>(root, taskId);
if (record.status !== "review-pending" || !record.reviewerPackage) {
throw new Error(
`Task ${taskId} has no pending review package. Run ${cliCommand(`finish --task ${taskId}`)} first.`,
);
}
emit(
io,
common.json,
record.reviewerPackage,
renderReviewHandoff(record, renderOptions(io, common)),
);
});

program
.command("context")
.description("produce bounded context for a task without invoking an agent")
Expand Down Expand Up @@ -1095,8 +1121,7 @@ export function createProgram(customIo?: Partial<Io>): Command {
reason: "No deterministic documentation signal was produced.",
},
learning: {
status:
learning.proposals.length > 0 ? ("proposed" as const) : ("no-candidate" as const),
status: learning.status,
proposals: learning.proposals.length,
},
};
Expand All @@ -1105,12 +1130,7 @@ export function createProgram(customIo?: Partial<Io>): Command {
io,
common.json,
{ record: finished, recordPath, completion, learning, retention },
renderGuidedFinish(
finished,
learning.proposals.length,
recordPath,
renderOptions(io, common),
),
renderGuidedFinish(finished, learning, recordPath, renderOptions(io, common)),
);
if (controller.signal.aborted) process.exitCode = EXIT.interrupted;
else if (finished.status === "incomplete" || finished.status === "failed")
Expand Down
12 changes: 11 additions & 1 deletion src/core/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const STOP_WORDS = new Set([
"do",
"existing",
"fix",
"first",
"for",
"from",
"has",
Expand All @@ -43,6 +44,7 @@ const STOP_WORDS = new Set([
"pattern",
"patterns",
"reuse",
"run",
"safe",
"safety",
"that",
Expand Down Expand Up @@ -81,6 +83,7 @@ const TEST_PATH =
const DOCUMENT_PATH = /(?:^|\/)(?:docs?|adr|adrs)(?:\/|$)|\.(?:md|mdx)$/;
const NON_AUTHORITATIVE_PATH =
/(?:^|\/)(?:expected|fixtures?|golden|snapshots?|examples?|generated|vendor|cassettes?|recordings?|testdata|canary|payloads?)(?:\/|$)|[.-]min\.(?:js|css)$/i;
const ACCEPTANCE_HARNESS_PATH = /(?:^|\/)tests?\/acceptance(?:\/|$)/i;

type Category = "entrypoint" | "manifest" | "source" | "test" | "document" | "other";

Expand Down Expand Up @@ -468,6 +471,9 @@ export async function buildContext(task: string, root = process.cwd()): Promise<
"testdata",
].includes(term),
);
const acceptanceHarnessRequested = taskTerms.some((term) =>
["acceptance", "adoption", "corpus", "harness", "release", "workflow"].includes(term),
);
const activeRoutes = (routes?.routes ?? []).filter((route) =>
route.match.some((pattern) => routeMatches(pattern, taskTerms)),
);
Expand Down Expand Up @@ -510,6 +516,10 @@ export async function buildContext(task: string, root = process.cwd()): Promise<
}
const candidate = baseScore(file, rankingTerms, activeRouteIds);
candidate.bytes = profile.fileSizes[file] ?? 0;
if (ACCEPTANCE_HARNESS_PATH.test(file) && !acceptanceHarnessRequested) {
candidate.score -= 60;
candidate.reasons.push("acceptance-harness penalty outside an acceptance task");
}
if (adoption.referencedPaths.includes(file)) {
candidate.score += 26;
candidate.reasons.push("explicitly referenced by repository instructions");
Expand Down Expand Up @@ -579,9 +589,9 @@ export async function buildContext(task: string, root = process.cwd()): Promise<
const priority = [
topOwner,
...topPathOwners,
topTest,
topProcedure,
...candidates.filter((item) => isAlwaysContext(item.file)),
topTest,
topDocument,
]
.filter((item): item is RankedCandidate => item !== undefined)
Expand Down
2 changes: 2 additions & 0 deletions src/core/proposals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ If start fails, stop before editing and report the error. If finish fails, do no

For unfinished work, use \`${cliCommand("status")}\` before opening raw task records. Even when status lists an active task, repeat start with that task's text before resuming edits: status is read-only and does not check write access. Keep routine output brief; use \`--verbose\` or \`--json\` when supporting detail is needed.

If finish reports \`review-pending\`, run \`${cliCommand("review --task <id>")}\` to expose the prepared package. Give its \`--json\` output to a fresh reviewer, save the strict response under \`.noxroot/local/\`, and pass that file back to finish.

When \`.noxroot/skills/\` exists, load only the task-relevant \`SKILL.md\`: verification for changed-code checks, independent review for fresh review, and product/UX review only for applicable user-facing work.`
: mode === "companion"
? `The existing repository coordinator remains authoritative for code-changing work. Noxroot does not add a second task lifecycle, reviewer, or learning loop.
Expand Down
4 changes: 4 additions & 0 deletions src/knowledge/learn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface LearningProposal {

export interface LearnResult {
taskId: string;
status: "not-assessed" | "no-candidate" | "proposed";
proposals: LearningProposal[];
rejected: Array<{ reason: string; destination: string }>;
message?: string;
Expand Down Expand Up @@ -217,6 +218,7 @@ export async function proposeLearnings(root: string, run: RunRecord): Promise<Le
if (!(await currentApprovedChange(root, run))) {
return {
taskId: run.id,
status: "not-assessed",
proposals: [],
rejected: [],
message: "Learning requires an approved review of the current unchanged diff",
Expand All @@ -228,6 +230,7 @@ export async function proposeLearnings(root: string, run: RunRecord): Promise<Le
if (candidates.length === 0) {
return {
taskId: run.id,
status: "no-candidate",
proposals: [],
rejected: [],
message: "No durable learning identified",
Expand Down Expand Up @@ -301,6 +304,7 @@ export async function proposeLearnings(root: string, run: RunRecord): Promise<Le
}
return {
taskId: run.id,
status: proposals.length > 0 ? "proposed" : "no-candidate",
proposals,
rejected,
...(proposals.length === 0 ? { message: "No durable learning identified" } : {}),
Expand Down
4 changes: 3 additions & 1 deletion src/orchestration/guided.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,9 @@ function guidedHandoff(
"LEARNING",
record.learningCandidates?.length
? `${record.learningCandidates.length} documentation candidate${record.learningCandidates.length === 1 ? "" : "s"} identified by review; duplication and ownership still need checking.`
: "No reusable project-knowledge candidate identified.",
: record.status === "approved"
? "The approved review identified no reusable project-knowledge candidate."
: "Not assessed; reusable learning requires an approved review of the unchanged diff.",
"",
"NEXT",
record.status === "review-pending"
Expand Down
Loading