snipara-companion 3.2.25 → 3.2.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  Release notes for `snipara-companion`, newest first.
4
4
 
5
+ ## New In 3.2.27
6
+
7
+ - Scopes `context-control drift` dirty Git detection to the ProjectContext
8
+ manifest, declared manifest sources, local Decision Requests, and
9
+ `.snipara/context-control/` artifacts. Dirty files outside that scope remain
10
+ visible but no longer force permanent `DRIFT_DETECTED` in shared checkouts.
11
+ - Clarifies the Context as Code V0 boundary: manifests are local declarative
12
+ metadata and do not refresh hosted context, reconcile manifest-vs-hosted
13
+ state, or mutate hosted memory. Hosted refresh/apply remains V1 work.
14
+ - Adds executable documentation smoke tests that run the README and full
15
+ reference `context-control` examples against the built local CLI, closing the
16
+ loop that previously allowed narrative examples to drift from command reality.
17
+
18
+ ## New In 3.2.26
19
+
20
+ - Fixes the published `context-control plan` examples so they use the real CLI
21
+ contract: `--summary` plus `--output`, without the nonexistent `--operation`
22
+ or `--content` flags or an out-of-scope target.
23
+ - Adds a package-doc regression test that executes the documented command shape
24
+ and rejects the unsupported flags if they return to the README or full
25
+ reference.
26
+
5
27
  ## New In 3.2.25
6
28
 
7
29
  - Adds `snipara-companion context-control plan` and `apply` for local,
package/README.md CHANGED
@@ -99,13 +99,11 @@ These commands are useful without hosted Snipara:
99
99
  `context-control` is the local trust layer for Project Intelligence state. It
100
100
  borrows Terraform's useful product grammar without copying Terraform: preview a
101
101
  bounded context mutation, inspect drift, then apply only the exact reviewed
102
- plan.
102
+ plan. V0 is intentionally local-only: it creates trust artifacts for review, not
103
+ hosted context mutations.
103
104
 
104
105
  ```bash
105
106
  npx -y snipara-companion context-control plan \
106
- --operation write_file \
107
- --target demo.json \
108
- --content '{"ok":true}' \
109
107
  --summary "record reviewed context state" \
110
108
  --output .snipara/context-control/plans/demo.json
111
109
 
@@ -126,13 +124,15 @@ locally:
126
124
  "path": "docs/architecture.md",
127
125
  "authority": "canonical",
128
126
  "tier": "HOT",
129
- "tags": ["architecture"]
127
+ "required": true,
128
+ "description": "Architecture context that agents should treat as canonical."
130
129
  }
131
130
  ],
132
131
  "policies": [
133
132
  {
134
133
  "id": "review-context-changes",
135
- "description": "Human review required before changing canonical context",
134
+ "scope": "memory.canonical",
135
+ "requirement": "Human review required before changing canonical context.",
136
136
  "reviewRequired": true
137
137
  }
138
138
  ]
@@ -145,7 +145,12 @@ npx -y snipara-companion context-control plan --manifest snipara.project-context
145
145
  ```
146
146
 
147
147
  The manifest is declarative metadata only. Validation and local reconciliation
148
- do not upload documents, approve memory, or mutate hosted Snipara state.
148
+ do not upload documents, approve memory, refresh hosted context, or mutate
149
+ hosted Snipara state. `context-control drift` scopes dirty Git signals to the
150
+ manifest, manifest sources, local Decision Requests, and `.snipara/context-control/`
151
+ artifacts so unrelated checkout noise does not become permanent drift. A future
152
+ V1 hosted refresh/apply surface should compare manifest state against hosted
153
+ context before allowing real hosted mutations.
149
154
 
150
155
  ### Local Worker Registry
151
156
 
package/dist/index.js CHANGED
@@ -33089,6 +33089,35 @@ function parseDirtyFile2(line) {
33089
33089
  const renameParts = withoutStatus.split(" -> ");
33090
33090
  return renameParts[renameParts.length - 1]?.replace(/^"|"$/g, "");
33091
33091
  }
33092
+ function resolveGitDriftScopePaths(cwd) {
33093
+ const scopePaths = [
33094
+ PROJECT_CONTEXT_MANIFEST_DEFAULT_PATH,
33095
+ CONTEXT_CONTROL_RELATIVE_DIR,
33096
+ path30.join(".snipara", "decisions")
33097
+ ];
33098
+ const manifestPath = resolveManifestPath(cwd);
33099
+ if (!fs30.existsSync(manifestPath)) {
33100
+ return normalizeScopePaths(scopePaths, cwd);
33101
+ }
33102
+ const manifestRead = readJsonFileSafe(manifestPath);
33103
+ if (!manifestRead.value) {
33104
+ return normalizeScopePaths(scopePaths, cwd);
33105
+ }
33106
+ const validation = validateProjectContextManifest({ manifest: manifestRead.value });
33107
+ const manifestSourcePaths = validation.manifest?.sources.map((source2) => source2.path) ?? [];
33108
+ return normalizeScopePaths([...scopePaths, ...manifestSourcePaths], cwd);
33109
+ }
33110
+ function normalizeScopePaths(scopePaths, cwd) {
33111
+ return uniqueStrings20(
33112
+ scopePaths.map((scopePath) => normalizeProjectRelativePath(scopePath, cwd).replace(/\/+$/g, ""))
33113
+ );
33114
+ }
33115
+ function isPathInsideScope(filePath, scopePaths) {
33116
+ const normalized = filePath.replace(/\\/g, "/").replace(/\/+$/g, "");
33117
+ return scopePaths.some(
33118
+ (scopePath) => normalized === scopePath || normalized.startsWith(`${scopePath}/`)
33119
+ );
33120
+ }
33092
33121
  function slugify2(value) {
33093
33122
  const slug2 = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 64);
33094
33123
  return slug2 || "context-control-state";
@@ -33380,25 +33409,39 @@ function readJsonFileSafe(filePath) {
33380
33409
  }
33381
33410
  function collectGitDriftSignals(cwd) {
33382
33411
  const revision = resolveGitBaseRevision(cwd);
33412
+ const scopePaths = resolveGitDriftScopePaths(cwd);
33413
+ const relevantDirtyFiles = revision.dirtyFiles.filter(
33414
+ (file) => isPathInsideScope(file, scopePaths)
33415
+ );
33383
33416
  const upstream = runGit3(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], cwd);
33384
33417
  const upstreamCounts = upstream ? runGit3(["rev-list", "--left-right", "--count", `${upstream}...HEAD`], cwd) : void 0;
33385
33418
  const [behind = 0, ahead = 0] = upstreamCounts ? upstreamCounts.split(/\s+/g).map((part) => Number.parseInt(part, 10) || 0) : [];
33386
- const dirtySignal = revision.dirty ? {
33387
- id: "git-working-tree-dirty",
33419
+ const dirtySignal = revision.dirty ? relevantDirtyFiles.length > 0 ? {
33420
+ id: "git-working-tree-dirty-relevant",
33388
33421
  surface: "git",
33389
33422
  state: "DRIFT_DETECTED",
33390
- summary: "Working tree has local uncommitted changes.",
33391
- expected: "Clean working tree for release-grade context-control reconciliation.",
33392
- observed: revision.dirtyFiles.join(", "),
33393
- refs: revision.dirtyFiles,
33423
+ summary: "Working tree has uncommitted changes in context-control or ProjectContext scope.",
33424
+ expected: "Manifest, manifest sources, Decision Requests, and context-control artifacts are clean.",
33425
+ observed: relevantDirtyFiles.join(", "),
33426
+ refs: relevantDirtyFiles,
33394
33427
  severity: "watch",
33395
- reasonCodes: ["git_working_tree_dirty"]
33428
+ reasonCodes: ["git_working_tree_dirty_relevant"]
33429
+ } : {
33430
+ id: "git-working-tree-dirty-out-of-scope",
33431
+ surface: "git",
33432
+ state: "IN_SYNC",
33433
+ summary: "Working tree has uncommitted changes, but none are in context-control or ProjectContext scope.",
33434
+ expected: `Only scoped dirty paths affect context-control drift: ${scopePaths.join(", ")}.`,
33435
+ observed: `${revision.dirtyFiles.length} out-of-scope dirty file(s)`,
33436
+ refs: revision.dirtyFiles,
33437
+ severity: "info",
33438
+ reasonCodes: ["git_working_tree_dirty_out_of_scope"]
33396
33439
  } : {
33397
33440
  id: "git-working-tree-clean",
33398
33441
  surface: "git",
33399
33442
  state: "IN_SYNC",
33400
- summary: "Working tree is clean.",
33401
- refs: [],
33443
+ summary: "Working tree has no uncommitted changes.",
33444
+ refs: scopePaths,
33402
33445
  severity: "info",
33403
33446
  reasonCodes: ["git_working_tree_clean"]
33404
33447
  };
@@ -33645,6 +33688,8 @@ function buildLocalProjectDriftReport(options = {}) {
33645
33688
  ],
33646
33689
  caveats: [
33647
33690
  "Project Drift V0 is a read-only local report.",
33691
+ "Git dirty-file drift is scoped to the ProjectContext manifest, manifest sources, Decision Requests, and .snipara/context-control artifacts; use git status for full working-tree release readiness.",
33692
+ "Project Drift V0 validates manifest health locally but does not refresh hosted context state; hosted manifest-vs-context reconciliation is V1 work.",
33648
33693
  "UNKNOWN is never treated as IN_SYNC; verify missing or unreadable evidence before applying changes."
33649
33694
  ]
33650
33695
  });
@@ -232,7 +232,7 @@ snipara-companion workflow sync-policy-ledger
232
232
  snipara-companion workflow producer-report
233
233
  snipara-companion workflow producer-review --latest --outcome useful --reviewer alice
234
234
  snipara-companion workflow run --adaptive-routing-dry-run --route-local-workers "document a scoped change"
235
- snipara-companion context-control plan --operation write_file --target demo.json --content '{"ok":true}' --summary "record reviewed context state"
235
+ snipara-companion context-control plan --summary "record reviewed context state" --output .snipara/context-control/plans/demo.json
236
236
  snipara-companion context-control apply --plan .snipara/context-control/plans/demo.json
237
237
  snipara-companion context-control drift
238
238
  snipara-companion context-control validate --manifest snipara.project-context.json
@@ -339,13 +339,18 @@ snipara-companion workflow resume --include-session-context
339
339
  Git bases by default, writes only under `.snipara/context-control/`, and emits
340
340
  an apply receipt linked to the plan hash.
341
341
  - `context-control drift` is a read-only Project Drift V0 report. It checks Git
342
- working tree/upstream state, managed workflow state, pending Decision
343
- Requests, saved plans and receipts, and ProjectContext manifest health.
344
- `UNKNOWN` is never treated as `IN_SYNC`.
342
+ upstream state, scoped Git dirty files, managed workflow state, pending
343
+ Decision Requests, saved plans and receipts, and ProjectContext manifest
344
+ health. Dirty Git files only become drift when they touch the ProjectContext
345
+ manifest, manifest sources, local Decision Requests, or
346
+ `.snipara/context-control/`; unrelated checkout noise remains visible without
347
+ classifying the project as `DRIFT_DETECTED`. `UNKNOWN` is never treated as
348
+ `IN_SYNC`.
345
349
  - `context-control validate --manifest snipara.project-context.json` validates
346
350
  Context as Code V0. The manifest is JSON metadata declaring context sources,
347
- tiers, authority, tags, owners, freshness, and review policies. It does not
348
- upload content or mutate hosted Snipara state.
351
+ tiers, authority, freshness, and review policies. It does not upload content,
352
+ refresh hosted context, reconcile manifest-vs-hosted state, or mutate hosted
353
+ Snipara state; hosted refresh/apply belongs to the future V1 surface.
349
354
  - `lead-plan` turns local workflow state, Team Sync, file scope, context refs,
350
355
  proof gates, and acceptance criteria into an advisory Engineering Lead Plan.
351
356
  It emits worker recommendations and handoff contracts, keeps
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snipara-companion",
3
- "version": "3.2.25",
3
+ "version": "3.2.27",
4
4
  "description": "Local-first CLI that asks your repo what breaks before an AI coding agent edits it.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {