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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

All notable changes to Context are documented here.

## 0.7.36 - 2026-09-22

- Clarify source inventory counts and failure reasons without treating retained workspace evidence as a new task's missing material.
- Preserve current workflow resource receipts when resuming tasks and refresh Routes after CLI upgrades.
- Recommend thirty documents per planning stage with a fifty-document maximum; retain one newly researched repository per stage.
- Require scope confirmation for broad discovery and human intervention above five hundred documents, including delegated planning.

## 0.7.35 - 2026-09-22

- Add the community `context-plan` Skill for project research, broad source updates, staged production handoffs and Git-backed plan recovery and completion.
Expand Down
30 changes: 15 additions & 15 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.7.35",
"version": "0.7.36",
"packageManager": "bun@1.3.9",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/context-cli/context-workflow/codes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,4 @@ codes:
- { code: route.indexer.candidate-review, kind: route-reason, summary: Review the exact current candidate set only after current mechanical validation passes. }
- { code: route.indexer.approved-knowledge-close, kind: route-reason, summary: Atomically close approved Indexer structure after required material gaps are resolved. }
- { code: route.indexer.source-update, kind: route-reason, summary: Inspect fixed source changes against current approved pages. }
- { code: route.workspace.task-cleared, kind: route-reason, summary: Task state is cleared and production awaits an explicit resume request., document: resources/procedures/workspace-prepare.md }
- { code: route.workspace.task-cleared, kind: route-reason, summary: No active production task; existing knowledge is retained and new production awaits explicit user intent., document: resources/procedures/workspace-prepare.md }
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ nodes:
resolutionAction: actions/resume-workspace-task.yaml
gate:
id: resume-workspace-task
prompt: Task state was cleared; retained knowledge does not prove all registered scope was delivered. For a capture-only request, register the named documents and reevaluate the capture Route without resuming production; report their outcomes and stop. Stay ready unless the user asks to resume production. With that explicit request, run task resume and follow the fresh Route.
prompt: No active production task is present. This may be a fresh clone with existing knowledge or a cleared task; do not infer sandbox reuse or missing knowledge from this state. For a capture-only request, register the named documents and reevaluate the capture Route without resuming production; report their outcomes and stop. Stay ready unless the user asks to resume production. With that explicit request, run task resume and follow the fresh Route.
delegatable: false
satisfiedBy:
- path: workspace.task_start_authorized
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.7.35
version: 0.7.36
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.7.35",
"version": "0.7.36",
"type": "module",
"license": "MIT",
"engines": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,13 @@ test("an initially unavailable source does not block independent writing and is
const plan = { stage: stage.id, capabilities: { skills: [] }, pending_scopes: [sources[1]!.source_ref],
articles: [{ path: "decision/first.md", question: "What was first observed?", sources: [sources[0]!.source_ref], batch: "first" }] };
await writeFile(join(agent, "submissions/plan.yaml"), YAML.stringify({ ...plan, pending_scopes: [] }));
await expect(submitProductionPlan({ projectRoot, stage: stage.id, path: "submissions/plan.yaml" })).rejects.toThrow("Keep unresolved material gaps");
await expect(submitProductionPlan({ projectRoot, stage: stage.id, path: "submissions/plan.yaml" })).rejects.toMatchObject({ detail: {
reason_code: "production-planning-refresh-required",
missing_pending_scopes: [sources[1]!.source_ref],
source_summary: { stage_source_count: 2, pending_scope_count: 2, unavailable_source_count: 1,
pending_without_read_failure_count: 1, planned_article_count: 0,
unavailable_sources: [{ scope: sources[1]!.source_ref, reason: expect.any(String) }] },
} });
await writeFile(join(agent, "submissions/plan.yaml"), YAML.stringify({ ...plan,
articles: [{ ...plan.articles[0]!, sources: [sources[1]!.source_ref] }] }));
await expect(submitProductionPlan({ projectRoot, stage: stage.id, path: "submissions/plan.yaml" })).rejects.toThrow("unavailable material");
Expand Down
22 changes: 22 additions & 0 deletions packages/context-cli/src/__tests__/productionSourceSummary.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { expect, test } from "bun:test";
import { productionSourceSummary } from "../project/productionSourceSummary.js";

test("large pending inventory is not reported as equally many unavailable sources", () => {
const scopes = Array.from({ length: 111 }, (_, i) => ({ scope: `source:${i}`, baseline: null }));
const result = productionSourceSummary({ scopes, pending_scopes: scopes.map(s => s.scope), tasks: [],
gaps: scopes.slice(0, 5).map(s => ({ scope: s.scope, reason: "Checkout missing" })) });
expect(result.stage_source_count).toBe(111);
expect(result.pending_scope_count).toBe(111);
expect(result.unavailable_source_count).toBe(5);
expect(result.pending_without_read_failure_count).toBe(106);
expect(result.planned_article_count).toBe(0);
expect(result.unavailable_sources).toHaveLength(5);
});

test("available sources and absent failures do not invent missing articles", () => {
const result = productionSourceSummary({ scopes: [{ scope: "note:a", baseline: null }], pending_scopes: [], gaps: [], tasks: [] });
expect(result.pending_scope_count).toBe(0);
expect(result.unavailable_source_count).toBe(0);
expect(result.planned_article_count).toBe(0);
expect(result.unavailable_sources).toEqual([]);
});
2 changes: 1 addition & 1 deletion packages/context-cli/src/project/indexerBaseContracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { bundledMarkdownReaderQuestionContracts } from
"./indexerBaseMarkdownAuthoringCatalog.js";

const BASE_CONTRACT_VERSION = "1.1.0";
export const BUNDLED_INDEXER_PARSER_PACKAGE_VERSION = "0.7.35";
export const BUNDLED_INDEXER_PARSER_PACKAGE_VERSION = "0.7.36";
const BUNDLED_PARSER_REQUIREMENTS = buildIndexerParserCapabilityRequirements(
BUNDLED_INDEXER_PARSER_PACKAGE_VERSION,
);
Expand Down
17 changes: 12 additions & 5 deletions packages/context-cli/src/project/productionPlanning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ import { readMaintenance } from "./maintenanceStorage.js";
import { withProductionFeedback } from "./productionFeedback.js";
import { resolveProductionExclusions } from "./productionExclusions.js";

function refreshRequired(stage: string, message: string): ContextError {
import { productionSourceSummary, productionSourceSummaryMarkdown } from "./productionSourceSummary.js";

function refreshRequired(stage: string, message: string, detail: Record<string, unknown> = {}): ContextError {
return new ContextError(ExitCode.UserError, message, {
...detail,
category: ErrorCategory.UserInputInvalid, reason_code: "production-planning-refresh-required",
next_action: { command: `context action prepare-current --revision ${stage} --format json` },
});
Expand Down Expand Up @@ -121,7 +124,7 @@ export async function prepareInitialProductionPlanning(input: { projectRoot: str
await writeProductionProjection(input.projectRoot, join(directory, "planning.md"), ["# Investigate and plan", "",
`Requirements: ${join(directory, "shared/requirements.md")}`, `Submission schema: ${join(directory, "planning.schema.json")}`,
`Existing reader topics: ${join(directory, "guidance/existing-articles.md")}`,
`Stage: ${stage.id}`, "", ...scopes.map(scope => `- ${scope.scope}: ${join(directory, productionSourceFile(scope.scope))}`), "",
`Stage: ${stage.id}`, "", ...productionSourceSummaryMarkdown(stage), ...scopes.map(scope => `- ${scope.scope}: ${join(directory, productionSourceFile(scope.scope))}`), "",
"Use code skeletons and document outlines to identify the authorized capability families and document tasks, then selectively read full material to decide reader topics. Navigation is not a complete feature inventory. Keep unchecked scope pending; do not parse all code or maintain per-symbol disposition just to plan.",
"Configured sources are the knowledge workspace coverage boundary, not a new investigation assignment on every request. First distinguish the user's current task, its actual source dependencies, and unrelated configured sources. Reuse approved content; a source-level pending entry alone does not prove missing knowledge or require new articles.",
"Report source baseline/read failures separately from content gaps. For an unrelated configured source, explain that its availability check is unresolved outside this task; do not promise a new code investigation. If the Route still requires resolution, report that precise workflow limitation without deleting source configuration, clearing runtime state, or claiming the source was investigated.",
Expand All @@ -136,7 +139,7 @@ export async function prepareInitialProductionPlanning(input: { projectRoot: str
`Write the plan to ${productionAgentDirectory(stage.id)}/submissions/plan.yaml and use the current action command. This does not approve the work-start report.`, ""].join("\n"));
await materializeProductionStage({ projectRoot: input.projectRoot, stage,
capabilities: productionCapabilitiesSchema.parse({}), materials: { ...prepared.materials, sources: sourceMaterials, guidance } });
return { stage_state: "active" as const, next: { directory, mode: "single-agent" as const } };
return { stage_state: "active" as const, source_summary: productionSourceSummary(stage), next: { directory, mode: "single-agent" as const } };
});
}

Expand All @@ -158,9 +161,13 @@ export async function submitProductionPlan(input: { projectRoot: string; stage:
if (new Set(pendingScopes).size !== pendingScopes.length || pendingScopes.some(scope => !stage.scopes.some(source => source.scope === scope))) {
throw new TypeError("Pending investigation must name unique authorized stage scopes");
}
if (stage.gaps.some(gap => !pendingScopes.includes(gap.scope))) throw refreshRequired(stage.id, "Keep unresolved material gaps in pending_scopes; restore the source if unavailable, then prepare the current stage to refresh its material snapshot");
if (stage.gaps.some(gap => !pendingScopes.includes(gap.scope))) throw refreshRequired(stage.id,
"Keep unresolved material gaps in pending_scopes. These are current material/baseline read failures, not evidence that knowledge was never captured. Restore required dependencies before preparing their snapshots; do not label every pending source as unavailable.", {
source_summary: productionSourceSummary(stage),
missing_pending_scopes: [...new Set(stage.gaps.filter(gap => !pendingScopes.includes(gap.scope)).map(gap => gap.scope))],
});
if (plan.articles.some(article => article.sources.some(scope => stage.gaps.some(gap => gap.scope === scope)))) {
throw refreshRequired(stage.id, "An article requires unavailable material in the captured stage. Restore the source if needed, then prepare the current stage; independent source plans may proceed");
throw refreshRequired(stage.id, "An article requires unavailable material in the captured stage. Restore the required source, including code dependencies of document-led work, then prepare the current stage; independent source plans may proceed.", { source_summary: productionSourceSummary(stage) });
}
for (const source of stage.scopes) if (!stage.gaps.some(gap => gap.scope === source.scope) &&
await productionSourceBaseline(input.projectRoot, source.scope) !== source.baseline) {
Expand Down
24 changes: 24 additions & 0 deletions packages/context-cli/src/project/productionSourceSummary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { ProductionStage } from "./productionStage.js";

/** Describe existing state only; never infer article coverage or alter scope. */
export function productionSourceSummary(stage: Pick<ProductionStage, "scopes" | "pending_scopes" | "gaps" | "tasks">) {
const pending = new Set(stage.pending_scopes);
const unavailable = new Set(stage.gaps.map(gap => gap.scope));
return {
stage_source_count: new Set(stage.scopes.map(source => source.scope)).size,
pending_scope_count: pending.size,
unavailable_source_count: unavailable.size,
pending_without_read_failure_count: [...pending].filter(scope => !unavailable.has(scope)).length,
planned_article_count: new Set(stage.tasks.filter(task => !["excluded", "replaced"].includes(task.status)).map(task => task.path)).size,
unavailable_sources: stage.gaps,
meaning: "Pending scopes are remaining investigation, not missing knowledge or failed captures. Unavailable sources have current material/baseline read failures; inspect their reasons. Counts can overlap and must not be added. Restore required code dependencies even for document-led work.",
};
}

export function productionSourceSummaryMarkdown(stage: Parameters<typeof productionSourceSummary>[0]): string[] {
const summary = productionSourceSummary(stage);
return ["## Source availability and progress", "",
`Stage sources: ${summary.stage_source_count}; pending investigation scopes: ${summary.pending_scope_count}; sources with read failures: ${summary.unavailable_source_count}; pending scopes without read failures: ${summary.pending_without_read_failure_count}; planned articles: ${summary.planned_article_count}.`,
summary.meaning, "",
...summary.unavailable_sources.map(gap => `- ${gap.scope}: ${gap.reason}`), ""];
}
4 changes: 3 additions & 1 deletion packages/context-cli/src/project/productionStageStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { withProductionFeedback } from "./productionFeedback.js";
import { dispatchProductionStage, validateProductionStage,
productionCapabilitiesSchema, type ProductionCapabilities, type ProductionDispatch, type ProductionStage } from "./productionStage.js";

import { productionSourceSummaryMarkdown } from "./productionSourceSummary.js";

export const PRODUCTION_STAGES_ROOT = ".tmp/context-runtime/production-stages";
const CURRENT_PATH = join(PRODUCTION_STAGES_ROOT, "current.json");

Expand Down Expand Up @@ -114,7 +116,7 @@ export function productionPlanMarkdown(stage: ProductionStage): string {
`Resubmit: context action complete-current --revision ${stage.id} --input ${productionAgentDirectory(stage.id)}/submissions/plan.yaml --format json`,
"Then present the updated report and apply context.gate.work_start_scope; an old decision does not approve changed article goals.", "",
] : []),
...stage.gaps.map(gap => `- ${gap.scope}: ${gap.reason}`), ""].join("\n");
...productionSourceSummaryMarkdown(stage)].join("\n");
}

/** Prepare directories before publishing issued state. A crash can leave an
Expand Down
1 change: 1 addition & 0 deletions packages/context-cli/src/project/statusCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ async function projectStatusSummary(status: ProjectStatus, projectRoot: string):
? "Progress only: workspace verification and delivery freshness were not checked. Delivery actions validate their inputs; context verify performs an explicit audit."
: "Approved pages may not yet be built. Package freshness describes dist; task completion describes the current stage only.",
},
counts_scope: "Workspace inventory, not this request’s additions or article workload. Captured documents, approved articles, pending investigation and source-read failures are different measures; source ready counts alone do not explain failure causes.",
counts: {
sources: status.sourceSummary,
draftCandidates: status.draftCandidates,
Expand Down
2 changes: 2 additions & 0 deletions packages/context-cli/src/project/workflow/workflowResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export type ContextWorkflowResourceAcknowledgeResult = ProjectStatus & {
protocol: "context.workflow.resource-receipts.v1";
acknowledged: number;
receiptReference: string;
message: string;
};
};

Expand Down Expand Up @@ -413,6 +414,7 @@ export async function acknowledgeCurrentWorkflowResources(input: {
protocol: "context.workflow.resource-receipts.v1",
acknowledged: directResources.length,
receiptReference: `@${join(found.projectRoot, continuation.path)}`,
message: "Read the returned workflow and follow its selected command unchanged, including revision and resource-receipt arguments. Do not pre-chain an earlier write command or replace this result with status without receipts. Acknowledgement does not necessarily change the revision; reread only changed or unavailable required content.",
},
};
}
Expand Down
2 changes: 1 addition & 1 deletion packages/context/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@c4a/context",
"description": "Declarative SDK for Context knowledge sources, workflows, review, and package outputs",
"version": "0.7.35",
"version": "0.7.36",
"type": "module",
"license": "MIT",
"engines": {
Expand Down
Loading
Loading