@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/config/rules/boundary/env-var-hygiene.yaml +13 -5
- package/package.json +1 -1
- package/plugins/sp/hooks/agent-hint.ts +1 -1
- package/plugins/sp/hooks/careful-guard.ts +1 -1
- package/plugins/sp/hooks/context-post-tool.ts +1 -1
- package/plugins/sp/hooks/context-session-start.ts +1 -1
- package/plugins/sp/hooks/context-session-stop.ts +1 -1
- package/plugins/sp/hooks/pi/guard-extension.ts +1 -1
- package/plugins/sp/hooks/task-write-guard.ts +1 -1
- package/plugins/sp/lib/env.ts +46 -0
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +1 -1
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +1 -1
- package/plugins/sp/scripts/feature-sync-bounded.mjs +1 -1
- package/plugins/sp/scripts/feature-sync-bounded.ts +1 -1
- package/plugins/sp/scripts/idea-handoff.mjs +1 -1
- package/plugins/sp/scripts/idea-handoff.ts +1 -1
- package/plugins/sp/scripts/inline-run-setup.ts +1 -1
- package/plugins/sp/scripts/pr-reviewing.mjs +1 -1
- package/plugins/sp/scripts/pr-reviewing.ts +1 -1
- package/plugins/sp/scripts/quality-gate.mjs +1 -1
- package/plugins/sp/scripts/quality-gate.ts +1 -1
- package/plugins/sp/scripts/task-evidence-precheck.ts +1 -1
- package/plugins/sp/scripts/task-size-precheck.ts +1 -1
- package/plugins/sp/scripts/verify-answer-lint.ts +1 -1
- package/plugins/sp/scripts/workflow-step-profile.mjs +1 -1
- package/plugins/sp/scripts/workflow-step-profile.ts +1 -1
- package/plugins/sp/scripts/wrapup-steps.mjs +1 -1
- package/plugins/sp/scripts/wrapup-steps.ts +1 -1
- package/spur.js +7 -7
|
@@ -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
|
|
13
|
-
#
|
|
14
|
-
#
|
|
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.
|
|
21
|
-
#
|
|
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.
|
|
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",
|
|
@@ -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 '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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';
|
|
@@ -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
|
+
}
|
package/plugins/sp/plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sp",
|
|
3
|
-
"version": "0.3.
|
|
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
|
-
//
|
|
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 '
|
|
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
|
-
//
|
|
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 '
|
|
27
|
+
import { getEnvVar } from '../lib/env';
|
|
28
28
|
|
|
29
29
|
// ── Local types (match packages/app FeatureService shapes; no package import) ───────────
|
|
30
30
|
|
|
@@ -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 '
|
|
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 {
|
|
@@ -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 '
|
|
25
|
+
import { getEnvVars } from '../lib/env';
|
|
26
26
|
|
|
27
27
|
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
28
28
|
|
|
@@ -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 '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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
|
-
//
|
|
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 '
|
|
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
|
-
//
|
|
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 '
|
|
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 =
|
|
111250
|
-
if (result.
|
|
111251
|
-
throw new Error(`bun install --frozen-lockfile failed after bump \u2014 bun.lock is inconsistent: ${result.stderr
|
|
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.
|
|
112208
|
+
binaryVersion: "0.3.89",
|
|
112209
112209
|
configDir: ".spur",
|
|
112210
112210
|
configFile: ".spur/config.yaml",
|
|
112211
112211
|
databaseFile: ".spur/spur.db"
|