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
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@ All notable changes to Context are documented here.

## Unreleased

## 0.6.13 - 2026-08-23

- Clarified debug startup so a new workspace receives `--debug` through
`context entry`/`context init`, while existing workspaces continue to use
`context debug enable`.
- Created the recommended `.tmp/agent-payloads/` scratch directory during
initialization and documented how to recreate it after scratch cleanup.

## 0.6.12 - 2026-08-22

- Added Agent guidance for publishing a completed Context build directly from
its output directory, while preserving human review by default and using
automatic merge only when the user explicitly requests direct publication.

## 0.6.11 - 2026-08-21

- Deferred code `source_ref` reverse lookup until every declared extraction phase is indexed, so a cold runtime continues the remaining phases instead of reporting provisional stale evidence.

## 0.6.10 - 2026-08-21

- Made repository recovery identity transport-independent by matching the full namespace/repository path while preserving pinned-commit and subpath validation.
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ Every build emits an inventory that maps distributed files back to approved
workspace knowledge. Published pages keep reader-facing metadata; detailed
source and review evidence remains in the production workspace.

Context stops after producing a complete local build output. Publishing that
output to a hosted knowledge service or package registry belongs to an
explicitly installed downstream distribution tool; use its complete-output
upload path when available instead of reconstructing a remote installation.

## Repository map

| Module | Role in the workflow | Documentation |
Expand Down
4 changes: 4 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ CLI 负责,它们不是可以随意修改的临时目录。只有当前工作
每次构建都会输出一份清单,将分发文件映射回工作区内的正式知识。发布页只保留
面向读者的元数据;完整来源和审核证据继续留在生产工作区。

Context 在生成完整本地产物后结束。将产物发布到托管知识服务或包平台,属于显式
安装的下游分发工具职责;如果分发工具支持完整产物上传,应直接使用该入口,而不
是先重建一份远端安装基线。

## 仓库模块

| 模块 | 在知识生产链中的职责 | 文档 |
Expand Down
18 changes: 9 additions & 9 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "context",
"version": "0.6.10",
"version": "0.6.13",
"packageManager": "bun@1.3.9",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/context-cli/context-workflow/provider.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
schema: agent-graph.provider.v1
id: c4a/context
version: 0.6.10
version: 0.6.13
name: Context workflow
description: Internal work contract for Context knowledge workspaces.
graphs:
Expand Down
2 changes: 1 addition & 1 deletion packages/context-cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@c4a/context-cli",
"description": "Local runtime and Agent integration for traceable knowledge production",
"version": "0.6.10",
"version": "0.6.13",
"type": "module",
"license": "MIT",
"engines": {
Expand Down
16 changes: 13 additions & 3 deletions packages/context-cli/plugin/commands/context.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,13 @@ Execute only `next_action.command` returned by `context entry`:

### Conversation modes

Enable debugging only when the user explicitly requests it. Run `context debug
enable` first; debugging records traces below `.tmp/context-runtime/debug/` but
does not grant workflow authority or provide source evidence.
Enable debugging only when the user explicitly requests it. If initialization
is required, pass `--debug` to `context entry` and execute its returned
`context init ... --debug` command; do not run a workspace-only debug command
before initialization. For an existing workspace, run `context debug enable`
before workflow evaluation. Debugging records traces below
`.tmp/context-runtime/debug/` but does not grant workflow authority or provide
source evidence.

For explicitly authorized fully managed operation, use:

Expand Down Expand Up @@ -115,6 +119,12 @@ Explain, ask, confirm, and summarize in the user's current conversation
language. Keep commands, flags, paths, ids, status values, JSONL keys,
`source_ref` values, and copied CLI tokens unchanged.

When the user explicitly asks to publish a completed build, treat publication
as a downstream distribution step outside the Context Route. Use only an
explicitly installed distribution tool and its documented complete-output
upload command. If no such tool is available, stop after the local build and
explain that Context itself does not publish to a hosted service.

Do not infer repo sources, extraction scope, review decisions, or package output
choices from surrounding files. Do not call source-repo operations such as
clone, checkout, reset, fetch, install, build, or test without explicit user
Expand Down
3 changes: 3 additions & 0 deletions packages/context-cli/src/__tests__/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ describe("CLI error handling", () => {
expect(existsSync(join(cwd, "parser-kb", ".tmp", "context-runtime", "lifecycle", "candidates.jsonl"))).toBe(false);
expect(existsSync(join(cwd, "parser-kb", "knowledge"))).toBe(true);
expect(existsSync(join(cwd, "parser-kb", "dist"))).toBe(true);
expect(existsSync(join(cwd, "parser-kb", ".tmp", "agent-payloads"))).toBe(true);
expect(existsSync(join(cwd, "parser-kb", "README.md"))).toBe(true);
expect(existsSync(join(cwd, "parser-kb", "AGENTS.md"))).toBe(true);
expect(existsSync(join(cwd, "parser-kb", ".claude"))).toBe(false);
Expand All @@ -329,6 +330,7 @@ describe("CLI error handling", () => {
expect(readme).toContain('TMPDIR="$PWD/.tmp/install" bun install');
expect(readme).toContain("`.tmp/install/`: workspace-local temporary files");
expect(readme).toContain("`.tmp/agent-payloads/`: optional Agent-owned command inputs");
expect(readme).toContain("Initialization creates it");
expect(readme).toContain("`.tmp/context-runtime/`: disposable runtime files");
expect(agents).toContain("Treat `workflow.current` as the current-step authority");
expect(agents).toContain("Read every `resources.required` item");
Expand All @@ -340,6 +342,7 @@ describe("CLI error handling", () => {
expect(agents).toContain("never store or reuse that authority");
expect(agents).toContain("Managed approval uses only the revision-bound atomic command");
expect(agents).toContain("Prefer `.tmp/agent-payloads/` for Agent-authored transient command inputs");
expect(agents).toContain("Initialization creates the directory");
expect(agents).toContain("not a CLI requirement");
expect(readme).not.toContain("bun run context -- status");
expect(agents).toContain("context status");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ describe("plugin prompt and workflow resource contract", () => {
expect(continuation).toContain("context plugin install");
expect(continuation).toContain("shell exit 127");
expect(continuation).toContain("Do not run an installation preflight");
expect(continuation).toContain("context init ... --debug");
expect(continuation).toContain("do not run a workspace-only debug command");
expect(continuation).toContain("For an existing workspace, run `context debug enable`");
expect(continuation).toContain("downstream distribution step outside the Context Route");
expect(continuation).toContain("Context itself does not publish to a hosted service");
});

test("source and gate discipline lives in selected workflow procedures", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,39 @@ async function writeRepoEvidence(projectRoot: string): Promise<void> {
}

describe("codegraph file-aware approved source refs", () => {
test("defers reverse lookup while the symbol index covers only part of the declared extraction phases", async () => {
const projectRoot = await makeProject();
try {
await writeRepoEvidence(projectRoot);
await writeApproved({
projectRoot,
collection: "codegraph",
sources: [`repo:${SOURCE_NAME}`],
sourceRef: `src-1#symbol:src/first.ts:Button:function@${SYMBOL_DIGEST}`,
body: "Button API evidence.",
extraFrontmatter: {
visibility: "exported",
code_symbols: [`${SOURCE_NAME}|Button|function`],
candidate_fingerprint: "sha256:candidate-fingerprint",
},
});

const result = await verifyProjectWorkspace(projectRoot, {
expectedExtractPhaseIds: [PHASE_ID, "extract:20260712/other:codegraph"],
});

expect(result.ok).toBe(true);
expect(result.evidenceStatus).toBe("pass-with-unverifiable-evidence");
expect(result.issues).toContainEqual(expect.objectContaining({
severity: "warning",
code: "extract-symbol-index-incomplete",
}));
expect(result.issues.map((issue) => issue.code)).not.toContain("approved-source-ref-stale");
} finally {
await rm(projectRoot, { recursive: true, force: true });
}
});

test("uses file identity to verify duplicate symbols without ambiguity", async () => {
const projectRoot = await makeProject();
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { describe, expect, test } from "bun:test";
import { execFileSync } from "node:child_process";
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { mkdir } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createCliProgram, handleCliFailure } from "../cli.js";
import { writeApproved } from "./projectVerifyV062Helpers.js";

function makeTmp(): string {
return mkdtempSync(join(tmpdir(), "ctx-project-batch-review-v069-"));
Expand Down Expand Up @@ -100,7 +101,95 @@ function writeMultiSourceProjectEntry(project: string): void {
].join("\n"), "utf8");
}

function writeRepoOnlyProjectEntry(project: string): void {
writeFileSync(join(project, "src", "index.ts"), [
'import { defineProject, extractTs, source } from "@c4a/context";',
"",
'const sampleA = source("20260712", "sample-a");',
'const sampleB = source("20260712", "sample-b");',
"",
"export default defineProject({",
" sources: [sampleA, sampleB],",
" phases: [",
' extractTs({ source: sampleA, collection: "codegraph" }),',
' extractTs({ source: sampleB, collection: "codegraph" }),',
" ],",
" packages: [],",
"});",
"",
].join("\n"), "utf8");
}

describe("0.6.9 codegraph batched human review", () => {
test("a cold runtime continues all extraction phases before validating existing code evidence", async () => {
const root = makeTmp();
const repoA = join(root, "repo-a");
const repoB = join(root, "repo-b");
const project = join(root, "kb");
try {
await mkdir(repoA, { recursive: true });
await mkdir(repoB, { recursive: true });
const headA = initTsRepo(repoA, "sample-a");
const headB = initTsRepo(repoB, "sample-b");
await runCliInDir(root, ["init", "kb"]);
for (const source of [
{ module: "sample-a", local: "../repo-a", remote: "https://git.example.com/repo-a.git", ref: headA },
{ module: "sample-b", local: "../repo-b", remote: "https://git.example.com/repo-b.git", ref: headB },
]) {
await runCliInDir(project, [
"source", "add", "repo", "20260712",
"--module", source.module,
"--local", source.local,
"--remote", source.remote,
"--ref", source.ref,
]);
}
writeRepoOnlyProjectEntry(project);
expect(existsSync(join(project, ".tmp", "context-runtime", "extract", "source-symbols.json"))).toBe(false);

await runCliInDir(project, [
"run", "extract:20260712/sample-a:codegraph", "--format", "json",
]);
await writeApproved({
projectRoot: project,
collection: "codegraph",
sources: ["repo:20260712/sample-b"],
sourceRef: "src-1#symbol:src/Button.ts:Button:function@abc123abc123",
body: "Existing sample B knowledge.",
extraFrontmatter: {
visibility: "exported",
code_symbols: ["20260712/sample-b|Button|function"],
candidate_fingerprint: "sha256:approved-sample-b",
},
});

const status = JSON.parse(await runCliInDir(project, ["status", "--format", "json", "--view", "full"])) as {
state: string;
verifyErrors: number;
pendingExtractPhases: string[];
routing: { command_plan: Array<{ command: string }> };
};
expect(status.state).toBe("route.extract.pending-target");
expect(status.verifyErrors).toBe(0);
expect(status.pendingExtractPhases).toEqual(["extract:20260712/sample-b:codegraph"]);
expect(status.routing.command_plan[0]?.command).toContain(
"run extract:20260712/sample-b:codegraph --format json",
);

const verify = JSON.parse(await runCliInDir(project, ["verify", "--format", "json"])) as {
ok: boolean;
evidence_status: string;
issues: Array<{ code: string }>;
};
expect(verify.ok).toBe(true);
expect(verify.evidence_status).toBe("pass-with-unverifiable-evidence");
expect(verify.issues.map((issue) => issue.code)).toContain("extract-symbol-index-incomplete");
expect(verify.issues.map((issue) => issue.code)).not.toContain("approved-source-ref-stale");
} finally {
rmSync(root, { recursive: true, force: true });
}
});

test("multiple confirmed repo modules finish extraction before one codegraph Review", async () => {
const root = makeTmp();
const repoA = join(root, "repo-a");
Expand Down
Loading