@gobing-ai/spur 0.3.88 → 0.3.89

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.
Files changed (31) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/rules/boundary/env-var-hygiene.yaml +13 -5
  3. package/package.json +1 -1
  4. package/plugins/sp/hooks/agent-hint.ts +1 -1
  5. package/plugins/sp/hooks/careful-guard.ts +1 -1
  6. package/plugins/sp/hooks/context-post-tool.ts +1 -1
  7. package/plugins/sp/hooks/context-session-start.ts +1 -1
  8. package/plugins/sp/hooks/context-session-stop.ts +1 -1
  9. package/plugins/sp/hooks/pi/guard-extension.ts +1 -1
  10. package/plugins/sp/hooks/task-write-guard.ts +1 -1
  11. package/plugins/sp/lib/env.ts +46 -0
  12. package/plugins/sp/plugin.json +1 -1
  13. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +1 -1
  14. package/plugins/sp/scripts/daily-summary/daily-summary.ts +1 -1
  15. package/plugins/sp/scripts/feature-sync-bounded.mjs +1 -1
  16. package/plugins/sp/scripts/feature-sync-bounded.ts +1 -1
  17. package/plugins/sp/scripts/idea-handoff.mjs +1 -1
  18. package/plugins/sp/scripts/idea-handoff.ts +1 -1
  19. package/plugins/sp/scripts/inline-run-setup.ts +1 -1
  20. package/plugins/sp/scripts/pr-reviewing.mjs +1 -1
  21. package/plugins/sp/scripts/pr-reviewing.ts +1 -1
  22. package/plugins/sp/scripts/quality-gate.mjs +1 -1
  23. package/plugins/sp/scripts/quality-gate.ts +1 -1
  24. package/plugins/sp/scripts/task-evidence-precheck.ts +1 -1
  25. package/plugins/sp/scripts/task-size-precheck.ts +1 -1
  26. package/plugins/sp/scripts/verify-answer-lint.ts +1 -1
  27. package/plugins/sp/scripts/workflow-step-profile.mjs +1 -1
  28. package/plugins/sp/scripts/workflow-step-profile.ts +1 -1
  29. package/plugins/sp/scripts/wrapup-steps.mjs +1 -1
  30. package/plugins/sp/scripts/wrapup-steps.ts +1 -1
  31. package/spur.js +7 -7
@@ -7,7 +7,7 @@
7
7
  "plugins": [
8
8
  {
9
9
  "name": "sp",
10
- "version": "0.3.88",
10
+ "version": "0.3.89",
11
11
  "source": "./plugins/sp"
12
12
  }
13
13
  ]
@@ -9,16 +9,18 @@ $schema: "@gobing-ai/spur/schemas/rule-file.schema.json"
9
9
  # Env flows into the app as typed config or injected `env` records (ADR-027). Runtime
10
10
  # options belong in `.spur/config.yaml` `bootstrap.options` via `getAppOptions`;
11
11
  # dev-only knobs belong in consts.
12
- # - Plugin scripts/hooks import the same functions from `@gobing-ai/ts-utils`; the
13
- # `superskill script convert` `.mjs` twins bundle them in (self-contained on any
14
- # install target).
12
+ # - Plugin scripts/hooks import `plugins/sp/lib/env.ts` — the vendored standalone
13
+ # gateway — because `superskill install` bundles them on targets with no
14
+ # node_modules, where any `@gobing-ai/*` import fails ("Bundle failed", task
15
+ # 0669). It mirrors the upstream semantics and is the one exempt file.
16
+ # The `superskill script convert` `.mjs` twins remain self-contained by inlining.
15
17
  # - `apps/web` uses `import.meta.env` (Vite browser plane) — not process env, never matches.
16
18
  # - Which variables exist and who consumes them is documented in `.env.example`.
17
19
  # (The former per-var lease table moved there; the rule now enforces the funnel,
18
20
  # not a file-by-var inventory.)
19
21
  #
20
- # Scope: apps/**, packages/**, scripts/**, plugins/sp/** — src AND tests. No exclusions:
21
- # even the gateway package itself is free of direct access.
22
+ # Scope: apps/**, packages/**, scripts/**, plugins/sp/** — src AND tests. One exclusion:
23
+ # the plugin's vendored gateway, whose sole job is direct `process.env` access.
22
24
  include:
23
25
  - "apps/**/src/**/*.{ts,tsx}"
24
26
  - "apps/**/tests/**/*.{ts,tsx}"
@@ -26,6 +28,12 @@ include:
26
28
  - "packages/**/tests/**/*.{ts,tsx}"
27
29
  - "scripts/**/*.ts"
28
30
  - "plugins/sp/**/*.ts"
31
+ exclude:
32
+ # plugins/sp/lib/env.ts is the vendored standalone env gateway for plugin
33
+ # hooks/scripts (bundled by superskill onto targets without node_modules);
34
+ # direct process.env access is its entire purpose. Mirrors the upstream
35
+ # ts-utils gateway — keep in sync (verified 2026-09).
36
+ - "plugins/sp/lib/env.ts"
29
37
  rules:
30
38
  - id: env-var-hygiene
31
39
  description: >
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/spur",
3
- "version": "0.3.88",
3
+ "version": "0.3.89",
4
4
  "description": "Spur CLI — local-first harness for mainstream coding agents: constraint checking, workflow orchestration, agent health, and history analytics. Bun-native; exposes the `spur` command.",
5
5
  "keywords": [
6
6
  "spur",
@@ -1,4 +1,4 @@
1
- import { getEnvVars } from '@gobing-ai/ts-utils';
1
+ import { getEnvVars } from '../lib/env';
2
2
  /**
3
3
  * Shared host-agent identification helpers — the **only** host-agent detection path
4
4
  * in the sp plugin hooks.
@@ -22,7 +22,7 @@
22
22
  * adapter. This file owns the Claude Code payload/decision shape only.
23
23
  */
24
24
 
25
- import { getEnvVar } from '@gobing-ai/ts-utils';
25
+ import { getEnvVar } from '../lib/env';
26
26
  import { classifyCommand } from './destructive-policy';
27
27
 
28
28
  interface ToolPayload {
@@ -23,7 +23,7 @@
23
23
  import { execSync } from 'node:child_process';
24
24
  import { appendFileSync, existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
25
25
  import { join, resolve, sep } from 'node:path';
26
- import { getEnvVar } from '@gobing-ai/ts-utils';
26
+ import { getEnvVar } from '../lib/env';
27
27
 
28
28
  /** Tools recorded by this hook (must match hooks.json PostToolUse matcher). */
29
29
  export const ALLOWED_TOOLS = new Set(['Bash', 'Grep', 'Glob', 'Read', 'Write', 'Edit']);
@@ -15,7 +15,7 @@
15
15
  import { execSync } from 'node:child_process';
16
16
  import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
17
17
  import { join } from 'node:path';
18
- import { getEnvVar, getEnvVars } from '@gobing-ai/ts-utils';
18
+ import { getEnvVar, getEnvVars } from '../lib/env';
19
19
  import { resolveAgentHint, resolveModelHint } from './agent-hint';
20
20
  import { checkContextFreshness } from './context-post-tool';
21
21
 
@@ -17,7 +17,7 @@
17
17
 
18
18
  import { appendFileSync, existsSync, readFileSync, rmSync } from 'node:fs';
19
19
  import { join } from 'node:path';
20
- import { getEnvVar } from '@gobing-ai/ts-utils';
20
+ import { getEnvVar } from '../lib/env';
21
21
 
22
22
  interface LedgerEvent {
23
23
  session: string;
@@ -23,7 +23,7 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync, realpathSync, rmSy
23
23
  import { homedir } from 'node:os';
24
24
  import { isAbsolute, join, resolve } from 'node:path';
25
25
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
26
- import { getEnvVar, getEnvVars } from '@gobing-ai/ts-utils';
26
+ import { getEnvVar, getEnvVars } from '../../lib/env';
27
27
  import { resolveAgentHint as resolveAgentHintShared, resolveModelHint as resolveModelHintShared } from '../agent-hint';
28
28
  import { classifyCommand } from '../destructive-policy';
29
29
  import { couldBeTaskFile } from '../task-file-policy';
@@ -20,7 +20,7 @@
20
20
  */
21
21
 
22
22
  import { spawnSync } from 'node:child_process';
23
- import { getEnvVar } from '@gobing-ai/ts-utils';
23
+ import { getEnvVar } from '../lib/env';
24
24
  import { couldBeTaskFile } from './task-file-policy';
25
25
 
26
26
  interface ToolPayload {
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Vendored env gateway for the sp plugin.
3
+ *
4
+ * WHY this copy exists: hooks and scripts in this plugin are bundled
5
+ * standalone by `superskill install` on targets that have no node_modules —
6
+ * any `@gobing-ai/*` import fails to resolve there and aborts the install
7
+ * ("Bundle failed", task 0669). This file mirrors the semantics of the
8
+ * canonical gateway (`@gobing-ai/ts-utils` dist/env, ts-libs) so the plugin
9
+ * stays self-contained. Keep in sync when the upstream gateway changes.
10
+ *
11
+ * These accessors are node-bun only: on `cloudflare-workers` there is no
12
+ * `process`; inject config explicitly rather than calling them (ADR-008).
13
+ */
14
+
15
+ /**
16
+ * Read one environment variable. Only an unset variable yields `fallback`;
17
+ * an empty string is a set value and is returned as-is.
18
+ */
19
+ export function getEnvVar(name: string, fallback?: string): string | undefined {
20
+ const raw = process.env[name];
21
+ return raw === undefined ? fallback : raw;
22
+ }
23
+
24
+ /**
25
+ * Read the live environment as a record. The returned object IS `process.env`
26
+ * (not a copy): whole-record operations see later mutations, which child-spawn
27
+ * composition relies on. For single variables prefer {@link getEnvVar}.
28
+ */
29
+ export function getEnvVars(): Record<string, string | undefined> {
30
+ return process.env;
31
+ }
32
+
33
+ /**
34
+ * Set one environment variable through the gateway. Passing `undefined` removes
35
+ * the key, so the save/restore idiom (`const prev = getEnvVar(k); …
36
+ * setEnvVar(k, prev)`) restores absence exactly.
37
+ */
38
+ export function setEnvVar(name: string, value: string | undefined): void {
39
+ if (value === undefined) delete process.env[name];
40
+ else process.env[name] = value;
41
+ }
42
+
43
+ /** Remove one environment variable; a no-op when the key is absent. */
44
+ export function removeEnvVar(name: string): void {
45
+ delete process.env[name];
46
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sp",
3
- "version": "0.3.88",
3
+ "version": "0.3.89",
4
4
  "description": "Spur — a local-first harness engineering toolkit that wraps mainstream coding agents with constraint checking, workflow orchestration, and history analytics.",
5
5
  "extensions": {
6
6
  "pi": ["./hooks/pi/guard-extension.ts"]
@@ -8,7 +8,7 @@ import { spawn, spawnSync } from "child_process";
8
8
  import { existsSync, mkdirSync, readlinkSync, writeFileSync } from "fs";
9
9
  import { dirname, isAbsolute, join, resolve } from "path";
10
10
 
11
- // ../ts-libs/packages/utils/dist/env.js
11
+ // plugins/sp/lib/env.ts
12
12
  function getEnvVar(name, fallback) {
13
13
  const raw = process.env[name];
14
14
  return raw === undefined ? fallback : raw;
@@ -11,7 +11,7 @@
11
11
  import { spawn, spawnSync } from 'node:child_process';
12
12
  import { existsSync, mkdirSync, readlinkSync, writeFileSync } from 'node:fs';
13
13
  import { dirname, isAbsolute, join, resolve } from 'node:path';
14
- import { getEnvVar, getEnvVars } from '@gobing-ai/ts-utils';
14
+ import { getEnvVar, getEnvVars } from '../../lib/env';
15
15
 
16
16
  import { logger } from './logger';
17
17
 
@@ -9,7 +9,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSy
9
9
  import { dirname } from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
11
 
12
- // ../ts-libs/packages/utils/dist/env.js
12
+ // plugins/sp/lib/env.ts
13
13
  function getEnvVar(name, fallback) {
14
14
  const raw = process.env[name];
15
15
  return raw === undefined ? fallback : raw;
@@ -24,7 +24,7 @@ import { createHash } from 'node:crypto';
24
24
  import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
25
25
  import { dirname } from 'node:path';
26
26
  import { fileURLToPath } from 'node:url';
27
- import { getEnvVar } from '@gobing-ai/ts-utils';
27
+ import { getEnvVar } from '../lib/env';
28
28
 
29
29
  // ── Local types (match packages/app FeatureService shapes; no package import) ───────────
30
30
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // @bun
3
3
 
4
- // ../ts-libs/packages/utils/dist/env.js
4
+ // plugins/sp/lib/env.ts
5
5
  function getEnvVars() {
6
6
  return process.env;
7
7
  }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- import { getEnvVars } from '@gobing-ai/ts-utils';
3
+ import { getEnvVars } from '../lib/env';
4
4
 
5
5
  /**
6
6
  * idea-handoff — portable entrypoint for the idea-pipeline finalization (task 0824, feature I21).
@@ -52,7 +52,7 @@
52
52
  import { appendFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
53
53
  import { dirname, join, resolve } from 'node:path';
54
54
  import { fileURLToPath } from 'node:url';
55
- import { getEnvVar } from '@gobing-ai/ts-utils';
55
+ import { getEnvVar } from '../lib/env';
56
56
 
57
57
  /** Outcome document written to `.spur/run/<run-id>-inline-setup.json`. */
58
58
  interface SetupOutcome {
@@ -6,7 +6,7 @@ import { spawnSync } from "child_process";
6
6
  import { existsSync, readFileSync, writeFileSync } from "fs";
7
7
  import { join } from "path";
8
8
 
9
- // ../ts-libs/packages/utils/dist/env.js
9
+ // plugins/sp/lib/env.ts
10
10
  function getEnvVars() {
11
11
  return process.env;
12
12
  }
@@ -22,7 +22,7 @@
22
22
  import { spawnSync } from 'node:child_process';
23
23
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
24
24
  import { join } from 'node:path';
25
- import { getEnvVars } from '@gobing-ai/ts-utils';
25
+ import { getEnvVars } from '../lib/env';
26
26
 
27
27
  // ─── Types ──────────────────────────────────────────────────────────────────
28
28
 
@@ -18,7 +18,7 @@ import {
18
18
  import { tmpdir } from "os";
19
19
  import { join } from "path";
20
20
 
21
- // ../ts-libs/packages/utils/dist/env.js
21
+ // plugins/sp/lib/env.ts
22
22
  function getEnvVars() {
23
23
  return process.env;
24
24
  }
@@ -41,7 +41,7 @@ import {
41
41
  } from 'node:fs';
42
42
  import { tmpdir } from 'node:os';
43
43
  import { join } from 'node:path';
44
- import { getEnvVars } from '@gobing-ai/ts-utils';
44
+ import { getEnvVars } from '../lib/env';
45
45
 
46
46
  export const MAX_GATE_ATTEMPTS = 5;
47
47
  export const MAX_FINDINGS = 20;
@@ -37,7 +37,7 @@ import { execFileSync } from 'node:child_process';
37
37
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
38
38
  import { join } from 'node:path';
39
39
  import { fileURLToPath } from 'node:url';
40
- import { getEnvVar } from '@gobing-ai/ts-utils';
40
+ import { getEnvVar } from '../lib/env';
41
41
 
42
42
  /** Exact task-content declaration that activates the live-evidence gate (0726 R2). */
43
43
  const DECLARATION_PREFIX = 'evidence-channel:';
@@ -26,7 +26,7 @@ import { execFileSync } from 'node:child_process';
26
26
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
27
27
  import { join } from 'node:path';
28
28
  import { fileURLToPath } from 'node:url';
29
- import { getEnvVar } from '@gobing-ai/ts-utils';
29
+ import { getEnvVar } from '../lib/env';
30
30
 
31
31
  // ─── Counting (sync with packages/app/src/services/task-size-precheck.ts) ────
32
32
 
@@ -32,7 +32,7 @@
32
32
  import { execFileSync } from 'node:child_process';
33
33
  import { existsSync, readFileSync } from 'node:fs';
34
34
  import { fileURLToPath } from 'node:url';
35
- import { getEnvVar } from '@gobing-ai/ts-utils';
35
+ import { getEnvVar } from '../lib/env';
36
36
 
37
37
  // ─── CLI (same spur-bin chain as task-evidence-precheck.ts) ─────────────────
38
38
 
@@ -6,7 +6,7 @@ import { spawnSync } from "child_process";
6
6
  import { existsSync } from "fs";
7
7
  import { fileURLToPath } from "url";
8
8
 
9
- // ../ts-libs/packages/utils/dist/env.js
9
+ // plugins/sp/lib/env.ts
10
10
  function getEnvVar(name, fallback) {
11
11
  const raw = process.env[name];
12
12
  return raw === undefined ? fallback : raw;
@@ -25,7 +25,7 @@
25
25
  import { spawnSync } from 'node:child_process';
26
26
  import { existsSync } from 'node:fs';
27
27
  import { fileURLToPath } from 'node:url';
28
- import { getEnvVar } from '@gobing-ai/ts-utils';
28
+ import { getEnvVar } from '../lib/env';
29
29
 
30
30
  // ── Input shapes (a subset of the live `spur workflow trace --json` payload) ─────────────
31
31
 
@@ -6,7 +6,7 @@ import { spawnSync } from "child_process";
6
6
  import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
7
7
  import { join } from "path";
8
8
 
9
- // ../ts-libs/packages/utils/dist/env.js
9
+ // plugins/sp/lib/env.ts
10
10
  function getEnvVars() {
11
11
  return process.env;
12
12
  }
@@ -31,7 +31,7 @@
31
31
  import { spawnSync } from 'node:child_process';
32
32
  import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
33
33
  import { join } from 'node:path';
34
- import { getEnvVars } from '@gobing-ai/ts-utils';
34
+ import { getEnvVars } from '../lib/env';
35
35
 
36
36
  export interface WrapupStepsEnv {
37
37
  __runId?: string;
package/spur.js CHANGED
@@ -111242,13 +111242,13 @@ async function run(repoRoot, cmd) {
111242
111242
  stderr: result.stderr.toString().trim()
111243
111243
  };
111244
111244
  }
111245
- function verifyLockfile(ctx, output2) {
111245
+ async function verifyLockfile(ctx, output2) {
111246
111246
  const lockPath = join38(ctx.repoRoot, "bun.lock");
111247
111247
  if (!existsSync13(lockPath) || Bun.file(lockPath).size === 0)
111248
111248
  return;
111249
- const result = Bun.spawnSync(["bun", "install", "--frozen-lockfile"], { cwd: ctx.repoRoot });
111250
- if (result.exitCode !== 0) {
111251
- throw new Error(`bun install --frozen-lockfile failed after bump \u2014 bun.lock is inconsistent: ${result.stderr.toString().trim()}`);
111249
+ const result = await run(ctx.repoRoot, ["bun", "install", "--frozen-lockfile"]);
111250
+ if (!result.ok) {
111251
+ throw new Error(`bun install --frozen-lockfile failed after bump \u2014 bun.lock is inconsistent: ${result.stderr}`);
111252
111252
  }
111253
111253
  output2.write(" \u21B3 bun.lock: verified (bun install --frozen-lockfile)");
111254
111254
  }
@@ -111520,7 +111520,7 @@ async function bumpVersion(ctx, config4, version3, options, output2) {
111520
111520
  staged.push(...await updateWorkspacePins(ctx, config4.packageName, previous, version3, output2));
111521
111521
  await syncMarketplaceAndPlugins(ctx, version3, staged, output2);
111522
111522
  await syncExtraCarriers(ctx, version3, staged, output2);
111523
- verifyLockfile(ctx, output2);
111523
+ await verifyLockfile(ctx, output2);
111524
111524
  const lockPath = join38(ctx.repoRoot, "bun.lock");
111525
111525
  if (existsSync13(lockPath) && Bun.file(lockPath).size > 0)
111526
111526
  staged.push("bun.lock");
@@ -111597,7 +111597,7 @@ async function bumpAll(ctx, version3, options, output2) {
111597
111597
  }
111598
111598
  await syncMarketplaceAndPlugins(ctx, version3, staged, output2);
111599
111599
  await syncExtraCarriers(ctx, version3, staged, output2);
111600
- verifyLockfile(ctx, output2);
111600
+ await verifyLockfile(ctx, output2);
111601
111601
  const lockPath = join38(ctx.repoRoot, "bun.lock");
111602
111602
  if (existsSync13(lockPath) && Bun.file(lockPath).size > 0)
111603
111603
  staged.push("bun.lock");
@@ -112205,7 +112205,7 @@ import { createRequire } from "module";
112205
112205
  var CLI_CONFIG = {
112206
112206
  binaryName: "spur",
112207
112207
  binaryLabel: "spur",
112208
- binaryVersion: "0.3.88",
112208
+ binaryVersion: "0.3.89",
112209
112209
  configDir: ".spur",
112210
112210
  configFile: ".spur/config.yaml",
112211
112211
  databaseFile: ".spur/spur.db"