litopencode 0.0.0

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 (55) hide show
  1. package/README.md +124 -0
  2. package/bin/litopencode +4 -0
  3. package/cover.png +0 -0
  4. package/dist/agents/defaults.d.ts +15 -0
  5. package/dist/agents/defaults.js +42 -0
  6. package/dist/agents/registry.d.ts +64 -0
  7. package/dist/agents/registry.js +31 -0
  8. package/dist/agents/specialists.d.ts +49 -0
  9. package/dist/agents/specialists.js +198 -0
  10. package/dist/agents/types.d.ts +30 -0
  11. package/dist/agents/types.js +4 -0
  12. package/dist/agents.d.ts +4 -0
  13. package/dist/agents.js +3 -0
  14. package/dist/cli.d.ts +8 -0
  15. package/dist/cli.js +204 -0
  16. package/dist/commands.d.ts +30 -0
  17. package/dist/commands.js +59 -0
  18. package/dist/config.d.ts +16 -0
  19. package/dist/config.js +61 -0
  20. package/dist/features.d.ts +108 -0
  21. package/dist/features.js +161 -0
  22. package/dist/hooks.d.ts +6 -0
  23. package/dist/hooks.js +11 -0
  24. package/dist/index.d.ts +15 -0
  25. package/dist/index.js +45 -0
  26. package/dist/ledger.d.ts +39 -0
  27. package/dist/ledger.js +147 -0
  28. package/dist/logger.d.ts +9 -0
  29. package/dist/logger.js +21 -0
  30. package/dist/skills.d.ts +47 -0
  31. package/dist/skills.js +60 -0
  32. package/dist/state.d.ts +14 -0
  33. package/dist/state.js +20 -0
  34. package/dist/tool-guards.d.ts +24 -0
  35. package/dist/tool-guards.js +82 -0
  36. package/dist/tools.d.ts +19 -0
  37. package/dist/tools.js +134 -0
  38. package/docs/migration.md +51 -0
  39. package/docs/release-checklist.md +120 -0
  40. package/generate_cover.py +127 -0
  41. package/package.json +29 -0
  42. package/skills/agent-roster/SKILL.md +26 -0
  43. package/skills/doctor-installer/SKILL.md +22 -0
  44. package/skills/durable-litgoal/SKILL.md +22 -0
  45. package/skills/litwork-activation/SKILL.md +25 -0
  46. package/skills/release-guardrails/SKILL.md +23 -0
  47. package/skills/tool-guards/SKILL.md +22 -0
  48. package/skills/workflow-loop/SKILL.md +24 -0
  49. package/tools/check-pack-payload.mjs +156 -0
  50. package/tools/check-version-lockstep.mjs +144 -0
  51. package/tools/run-build.mjs +34 -0
  52. package/tools/run-typecheck.mjs +25 -0
  53. package/tools/scan-legacy-tokens.mjs +211 -0
  54. package/tsconfig.build.json +12 -0
  55. package/tsconfig.json +13 -0
@@ -0,0 +1,51 @@
1
+ # LitOpenCode Migration Guide
2
+
3
+ This guide describes the supported migration shape for moving an OpenCode workflow adapter to LitOpenCode. Keep this document brand-clean for public package payloads.
4
+
5
+ ## Package
6
+
7
+ - Use `litopencode` as the npm package name, OpenCode plugin id, and CLI binary name.
8
+ - Configure OpenCode plugin loading with `litopencode`; do not carry old adapter package names into new runtime config.
9
+ - Treat upstream or sibling repositories as behavior references only. Product code in this package is OpenCode-native TypeScript authored for this repo.
10
+
11
+ ## Environment And Config
12
+
13
+ - Use the `LITOPENCODE_` prefix for LitOpenCode-owned environment variables.
14
+ - Keep OpenCode plugin configuration in `opencode.json`.
15
+ - Keep LitOpenCode runtime config in `.litopencode/config.json` when project-local config is needed.
16
+ - Use `litopencode doctor --root <workspace>` to inspect package metadata, config source, runtime paths, and ledger location without writing files.
17
+ - Use `litopencode install --dry-run --root <workspace>` to preview the `opencode.json` plugin mutation before applying it manually or through a future write-enabled installer.
18
+
19
+ ## Durable State
20
+
21
+ LitOpenCode runtime state is intentionally separated from planning and agent evidence:
22
+
23
+ - project runtime root: `.litopencode/`
24
+ - durable goal root: `.litopencode/litgoal/`
25
+ - durable ledger: `.litopencode/litgoal/lit-loop/ledger.jsonl`
26
+ - user/global root: `~/.litopencode`
27
+ - implementation evidence root: `.litcodex/lit-loop/evidence/`
28
+
29
+ The durable ledger uses atomic write behavior and recovery for partial temporary files. Existing state from a previous adapter should be copied only after a deliberate review, then represented as LitOpenCode ledger events rather than mixed directly into the new ledger file.
30
+
31
+ ## Vocabulary
32
+
33
+ Use LitOpenCode vocabulary in docs, config, runtime output, and issue reports:
34
+
35
+ - product: `LitOpenCode`
36
+ - package, plugin, and binary: `litopencode`
37
+ - activation commands/tools: `lit`, `litwork`
38
+ - durable loop: `lit-loop`
39
+ - durable goal manager: `litgoal`
40
+ - planning/research workflow names: `lit-plan`, `litresearch`, `start-work`, `review-work`, `deep-interview`, `dynamic-workflow`
41
+
42
+ Legacy vocabulary may appear only in retained provenance files covered by the scanner allowlist and removal conditions. Do not introduce old package names, host claims, or environment prefixes into new product docs, tests, source, or release payloads.
43
+
44
+ ## Migration Checklist
45
+
46
+ - Replace package/config references with `litopencode`.
47
+ - Move LitOpenCode-owned environment settings to `LITOPENCODE_` names.
48
+ - Keep runtime state under `.litopencode/litgoal` and implementation evidence under `.litcodex`.
49
+ - Keep visible static skills under `skills/*/SKILL.md`; they document workflow loop, durable litgoal, agent roster, doctor installer, release guardrails, litwork activation, and tool guards without dynamic execution.
50
+ - Run `npm run scan:legacy-tokens` after any migration wording change.
51
+ - Run `npm run check:pack-payload` and `npm pack --dry-run --json` before sharing a package artifact.
@@ -0,0 +1,120 @@
1
+ # LitOpenCode Release Checklist
2
+
3
+ This checklist is a readiness gate, not an instruction to distribute. The current CI and local workflow are no-publication by default.
4
+
5
+ ## Preflight
6
+
7
+ - Confirm `package.json` name, binary, and plugin id are `litopencode`.
8
+ - Confirm `package.json` and `package-lock.json` versions are in lockstep.
9
+ - Confirm `README.md`, `docs/migration.md`, `docs/release-checklist.md`, and `HANDOFF.md` describe the current implementation state.
10
+ - Confirm `.litopencode/`, `.litcodex/`, generated evidence, reference archives, fixture-only paths, and package archives are excluded from the packed payload.
11
+
12
+ ## Required Gates
13
+
14
+ Run these commands from the repository root:
15
+
16
+ ```sh
17
+ npm run build
18
+ npm test
19
+ npm test
20
+ npm run typecheck
21
+ npm run scan:legacy-tokens
22
+ npm run check:version
23
+ npm run check:pack-payload
24
+ npm pack --dry-run --json
25
+ ```
26
+
27
+ Expected results:
28
+
29
+ - tests pass twice to reduce stale or misleading success output risk
30
+ - build emits `dist/*.js` and `dist/*.d.ts`
31
+ - typecheck exits cleanly
32
+ - scanner exits cleanly with only current allowlisted provenance hits
33
+ - version lockstep exits cleanly
34
+ - payload guard exits cleanly
35
+ - dry pack emits a manifest and leaves no root package archive behind
36
+
37
+ ## Packed Artifact Probe
38
+
39
+ After the source gates pass, verify the packed artifact in a temporary directory:
40
+
41
+ ```sh
42
+ tmp="$(mktemp -d)"
43
+ npm pack --pack-destination "$tmp"
44
+ tar -xzf "$tmp"/litopencode-0.0.0.tgz -C "$tmp"
45
+ node --input-type=module -e "import('$tmp/package/dist/index.js').then((m) => console.log(m.default?.id ?? m.pluginId))"
46
+ (
47
+ cd "$tmp/package"
48
+ npm run scan:legacy-tokens
49
+ npm run check:version
50
+ npm run check:pack-payload
51
+ npm run typecheck
52
+ )
53
+ rm -rf "$tmp"
54
+ ```
55
+
56
+ Expected results:
57
+
58
+ - plugin import prints `litopencode`
59
+ - package payload includes compiled `dist/*.js` and `dist/*.d.ts` files, not `src/*.ts`
60
+ - `scan:legacy-tokens` runs with an empty allowlist when the source-only allowlist is absent from the packed artifact
61
+ - `check:version` skips lockstep when the source-only lockfile is absent from the packed artifact
62
+ - `typecheck` skips when TypeScript dev dependencies are not installed in the packed artifact
63
+ - `check:pack-payload` still validates the packed manifest
64
+
65
+ ## Installed Package Probe
66
+
67
+ After the packed artifact probe, verify the public install surface in a clean temporary npm project:
68
+
69
+ ```sh
70
+ tmp="$(mktemp -d)"
71
+ npm pack --pack-destination "$tmp"
72
+ mkdir "$tmp/consumer"
73
+ (
74
+ cd "$tmp/consumer"
75
+ npm init -y
76
+ npm install "$tmp"/litopencode-0.0.0.tgz
77
+ npm ls litopencode @opencode-ai/plugin --all
78
+ node --input-type=module -e "import('litopencode').then((m) => console.log(m.default.id))"
79
+ node_modules/.bin/litopencode --help
80
+ node_modules/.bin/litopencode doctor --root .
81
+ node_modules/.bin/litopencode install --dry-run --root .
82
+ )
83
+ rm -rf "$tmp"
84
+ ```
85
+
86
+ Expected results:
87
+
88
+ - `@opencode-ai/plugin` is installed as a runtime dependency below `litopencode`
89
+ - package import prints `litopencode`
90
+ - CLI help, doctor, and install dry-run exit 0
91
+ - temporary project cleanup removes the probe directory
92
+
93
+ ## OpenCode Host Probe
94
+
95
+ Use the installed temp-project package, not the source tree, to import `litopencode`, call the default plugin module\'s `server()` function, invoke the config hook, command hook, `lit` and `litwork` tools, and before/after tool guard hooks. Expected results:
96
+
97
+ - `server()` exposes config, tool, command activation, dispose, and non-enumerable tool guard hooks
98
+ - config registers the LitOpenCode agent roster
99
+ - `/litwork` command activation records a durable redacted ledger event
100
+ - `lit` and `litwork` tools record durable ledger events
101
+ - malformed LitOpenCode tool args are denied fail-closed
102
+
103
+ ## No-Publication Guardrails
104
+
105
+ - Do not run the npm registry publication command in this workflow.
106
+ - Do not push repository refs from this workflow.
107
+ - Do not create or move repository tags from this workflow.
108
+ - Do not add npm or node auth token values, token variables, or registry credentials to local files, CI, evidence, or docs.
109
+ - Do not add a release job, deploy job, publication secret, write permission, or history rewrite step without explicit user authorization.
110
+ - CI must keep `permissions: contents: read` and must continue to run tests, typecheck, scanner, version lockstep, dry pack, and payload guard.
111
+
112
+ ## Payload Review
113
+
114
+ Use `npm pack --dry-run --json --ignore-scripts` after `npm run build` as the observable package surface for payload checking. The manifest must include only public package files required for the compiled JavaScript plugin, type declarations, CLI, docs, and guard scripts. It must not include local state, implementation evidence, reference archives, temporary fixtures, dependency folders, source TypeScript, or generated package archives. Source-only guard inputs such as `package-lock.json` and `tools/legacy-token-allowlist.json` stay out of the public payload; their scripts must degrade explicitly and safely when those inputs are absent in the packed artifact.
115
+
116
+ ## Final Verification Reminder
117
+
118
+ FV-ALL is complete from `.litcodex/plans/litopencode-rebrand-sdd.md`. Final evidence is recorded at `.litcodex/lit-loop/evidence/start-work/FV-ALL-final.txt`.
119
+
120
+ The completed final wave drove the package through an OpenCode-matching plugin surface and included `npm test`, `npm run typecheck`, `npm run scan:legacy-tokens`, `npm run check:version`, `npm run check:pack-payload`, `npm pack --dry-run --json`, CLI dry-run probes, durable ledger probes, docs tests, and runtime skills coverage.
@@ -0,0 +1,127 @@
1
+ """
2
+ Generate the LitOpenCode cover image (2560x1280).
3
+
4
+ The visual style follows the sibling cover scripts in this workspace: a dark
5
+ gradient field, soft glow, monospace product title, and a compact subtitle.
6
+ """
7
+
8
+ from pathlib import Path
9
+
10
+ import numpy as np
11
+ from PIL import Image, ImageDraw, ImageFilter, ImageFont
12
+
13
+
14
+ W, H = 2560, 1280
15
+ CORNER_RADIUS = 80
16
+ ROOT = Path(__file__).resolve().parent
17
+ OUT_PATH = ROOT / "cover.png"
18
+
19
+
20
+ def make_blob(size, color_rgba, cx, cy, rx, ry):
21
+ layer = Image.new("RGBA", size, (0, 0, 0, 0))
22
+ draw = ImageDraw.Draw(layer)
23
+ draw.ellipse([cx - rx, cy - ry, cx + rx, cy + ry], fill=color_rgba)
24
+ return layer
25
+
26
+
27
+ def load_font(size, bold=False):
28
+ try:
29
+ return ImageFont.truetype("/System/Library/Fonts/Menlo.ttc", size, index=1 if bold else 0)
30
+ except Exception:
31
+ fallback = "/System/Library/Fonts/Supplemental/Courier New Bold.ttf"
32
+ return ImageFont.truetype(fallback, size)
33
+
34
+
35
+ def draw_text_layer(text, x, y, font, color):
36
+ layer = Image.new("RGBA", (W, H), (0, 0, 0, 0))
37
+ ImageDraw.Draw(layer).text((x, y), text, font=font, fill=color)
38
+ return layer
39
+
40
+
41
+ base = Image.new("RGBA", (W, H), (9, 11, 18, 255))
42
+
43
+ blobs = [
44
+ (37, 99, 235, 150, 620, 360, 720, 520, 125),
45
+ (245, 158, 11, 150, 1880, 400, 620, 460, 105),
46
+ (20, 184, 166, 120, 1320, 1040, 980, 360, 95),
47
+ (168, 85, 247, 105, 1420, 650, 560, 380, 85),
48
+ ]
49
+
50
+ canvas = base.copy()
51
+ for r, g, b, a, cx, cy, rx, ry, blur in blobs:
52
+ blob = make_blob((W, H), (r, g, b, a), cx, cy, rx, ry)
53
+ blob = blob.filter(ImageFilter.GaussianBlur(radius=blur))
54
+ canvas = Image.alpha_composite(canvas, blob)
55
+
56
+ canvas = canvas.filter(ImageFilter.GaussianBlur(radius=8))
57
+
58
+ rng = np.random.default_rng(42)
59
+ noise = rng.integers(0, 255, (H, W), dtype=np.uint8)
60
+ grain_alpha = (noise * 0.16).astype(np.uint8)
61
+ grain_layer = np.stack([noise, noise, noise, grain_alpha], axis=-1).astype(np.uint8)
62
+ canvas = Image.alpha_composite(canvas, Image.fromarray(grain_layer, "RGBA"))
63
+
64
+ TITLE_TEXT = "litopencode"
65
+ SUBTITLE_TEXT = "OpenCode plugin for lit-plan, lit-loop, and durable release gates."
66
+
67
+ tmp = Image.new("RGBA", (W, H), (0, 0, 0, 0))
68
+ d = ImageDraw.Draw(tmp)
69
+
70
+ font_title = load_font(224, bold=True)
71
+ font_subtitle = load_font(64)
72
+
73
+ t_bbox = d.textbbox((0, 0), TITLE_TEXT, font=font_title)
74
+ t_w = t_bbox[2] - t_bbox[0]
75
+ t_h = t_bbox[3] - t_bbox[1]
76
+
77
+ max_subtitle_width = W - 360
78
+ while True:
79
+ s_bbox = d.textbbox((0, 0), SUBTITLE_TEXT, font=font_subtitle)
80
+ s_w = s_bbox[2] - s_bbox[0]
81
+ if s_w <= max_subtitle_width:
82
+ break
83
+ current_size = getattr(font_subtitle, "size", 64)
84
+ if current_size <= 42:
85
+ break
86
+ font_subtitle = load_font(current_size - 2)
87
+
88
+ s_bbox = d.textbbox((0, 0), SUBTITLE_TEXT, font=font_subtitle)
89
+ s_w = s_bbox[2] - s_bbox[0]
90
+ s_h = s_bbox[3] - s_bbox[1]
91
+
92
+ gap = 52
93
+ total_h = t_h + gap + s_h
94
+ block_top = (H - total_h) // 2 - 20
95
+
96
+ title_x = (W - t_w) // 2 - t_bbox[0]
97
+ title_y = block_top - t_bbox[1]
98
+ subtitle_x = (W - s_w) // 2 - s_bbox[0]
99
+ subtitle_y = title_y + t_h + gap
100
+
101
+ for color, blur_r in [
102
+ ((96, 165, 250, 70), 22),
103
+ ((20, 184, 166, 90), 11),
104
+ ((251, 191, 36, 110), 5),
105
+ ]:
106
+ glow = draw_text_layer(TITLE_TEXT, title_x, title_y, font_title, color)
107
+ glow = glow.filter(ImageFilter.GaussianBlur(radius=blur_r))
108
+ canvas = Image.alpha_composite(canvas, glow)
109
+
110
+ canvas = Image.alpha_composite(
111
+ canvas,
112
+ draw_text_layer(TITLE_TEXT, title_x, title_y, font_title, (248, 250, 252, 246)),
113
+ )
114
+ canvas = Image.alpha_composite(
115
+ canvas,
116
+ draw_text_layer(SUBTITLE_TEXT, subtitle_x, subtitle_y, font_subtitle, (203, 213, 225, 222)),
117
+ )
118
+
119
+ mask = Image.new("L", (W, H), 0)
120
+ ImageDraw.Draw(mask).rounded_rectangle([(0, 0), (W - 1, H - 1)], radius=CORNER_RADIUS, fill=255)
121
+ canvas.putalpha(mask)
122
+ canvas = canvas.filter(ImageFilter.GaussianBlur(radius=1))
123
+
124
+ canvas.save(OUT_PATH, "PNG", dpi=(400, 400))
125
+ print(f"Saved: {OUT_PATH}")
126
+ print(f"Size: {canvas.size}")
127
+ print(f"Mode: {canvas.mode}")
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "litopencode",
3
+ "version": "0.0.0",
4
+ "description": "LitOpenCode OpenCode plugin bootstrap package.",
5
+ "type": "module",
6
+ "bin": {
7
+ "litopencode": "bin/litopencode"
8
+ },
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": "./dist/index.js"
12
+ },
13
+ "scripts": {
14
+ "build": "node tools/run-build.mjs",
15
+ "check:pack-payload": "npm run build && npm pack --dry-run --json --ignore-scripts | node tools/check-pack-payload.mjs --stdin",
16
+ "check:version": "node tools/check-version-lockstep.mjs",
17
+ "prepack": "npm run build",
18
+ "scan:legacy-tokens": "node tools/scan-legacy-tokens.mjs",
19
+ "test": "node --test",
20
+ "typecheck": "node tools/run-typecheck.mjs"
21
+ },
22
+ "dependencies": {
23
+ "@opencode-ai/plugin": "^1.17.6"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^24.12.2",
27
+ "typescript": "^5.9.3"
28
+ }
29
+ }
@@ -0,0 +1,26 @@
1
+ # Agent Roster
2
+
3
+ Use this LitOpenCode skill when a contributor needs the static agent-roster map for `agent-roster`.
4
+
5
+ ## Covers
6
+
7
+ - Provide the two primary user-facing agents: `lit-plan` and `lit-loop`.
8
+ - Keep recommended role aliases available as explicit subagent choices.
9
+ - Keep specialist agents available as advanced explicit choices.
10
+ - Merge agent definitions through the OpenCode config hook without overwriting host agents.
11
+ - Keep prompts brand-clean and OpenCode-native.
12
+
13
+ ## OpenCode Surfaces
14
+
15
+ - Config hook: LitOpenCode agent merge
16
+ - Primary default agents: `lit-plan`, `lit-loop`
17
+ - Recommended role aliases: `lit-architect`, `lit-forge`, `lit-oracle`, `lit-prover`, `lit-sentinel`, `lit-librarian`
18
+ - Specialist agents: explorer, archive researcher, verdict oracle, strategy planner, forge worker, systems architect, critical reviewer, context cartographer, persistence runner
19
+ - Runtime feature id: `agent-roster`
20
+ - Runtime feature id: `planning-start-work-loop`
21
+
22
+ ## Safety
23
+
24
+ - This file is static documentation.
25
+ - Do not execute commands from this file automatically.
26
+ - Preserve existing host agent entries during registration.
@@ -0,0 +1,22 @@
1
+ # Doctor Installer
2
+
3
+ Use this LitOpenCode skill when a contributor needs the static CLI health and installer map for `doctor-install`.
4
+
5
+ ## Covers
6
+
7
+ - Inspect package metadata, config source, runtime paths, and state presence.
8
+ - Preview OpenCode plugin configuration changes without writing files.
9
+ - Keep malformed config handling fail-closed.
10
+ - Keep installer output bounded and avoid leaking existing user config content.
11
+
12
+ ## OpenCode Surfaces
13
+
14
+ - CLI: `litopencode doctor`
15
+ - CLI: `litopencode install --dry-run`
16
+ - Runtime feature id: `doctor-install`
17
+
18
+ ## Safety
19
+
20
+ - This file is static documentation.
21
+ - Do not execute commands from this file automatically.
22
+ - Treat dry-run output as a summary of intended mutation, not as a full config dump.
@@ -0,0 +1,22 @@
1
+ # Durable LitGoal
2
+
3
+ Use this LitOpenCode skill when a contributor needs the static durable-goal map for `durable-ledger`.
4
+
5
+ ## Covers
6
+
7
+ - Store resumable goal and loop state under `.litopencode/litgoal`.
8
+ - Append durable JSONL events for command, tool, and goal activity.
9
+ - Recover temporary ledger files before status reads.
10
+ - Fail closed when durable ledger lines are malformed.
11
+
12
+ ## OpenCode Surfaces
13
+
14
+ - Tool: `litwork` with `status`
15
+ - Runtime state: `.litopencode/litgoal/lit-loop/ledger.jsonl`
16
+ - Runtime feature id: `durable-ledger`
17
+
18
+ ## Safety
19
+
20
+ - This file is static documentation.
21
+ - Do not execute commands from this file automatically.
22
+ - Keep ledger events JSON-serializable and redact command arguments before persistence.
@@ -0,0 +1,25 @@
1
+ # Litwork Activation
2
+
3
+ Use this LitOpenCode skill when a contributor needs the static activation-surface map for `lit-litwork-activation`.
4
+
5
+ ## Covers
6
+
7
+ - Expose `/lit` and `/litwork` as command activation points.
8
+ - Expose `lit` and `litwork` as OpenCode tools.
9
+ - Record activation as durable ledger metadata without persisting raw command arguments.
10
+ - Keep activation text observable through command hooks.
11
+
12
+ ## OpenCode Surfaces
13
+
14
+ - Command: `/lit`
15
+ - Command: `/litwork`
16
+ - Tool: `lit`
17
+ - Tool: `litwork`
18
+ - Hook: `command.execute.before`
19
+ - Runtime feature id: `lit-litwork-activation`
20
+
21
+ ## Safety
22
+
23
+ - This file is static documentation.
24
+ - Do not execute commands from this file automatically.
25
+ - Persist redacted argument metadata only.
@@ -0,0 +1,23 @@
1
+ # Release Guardrails
2
+
3
+ Use this LitOpenCode skill when a contributor needs the static release-readiness map for `guardrails`.
4
+
5
+ ## Covers
6
+
7
+ - Run scanner, version lockstep, payload guard, dry pack, typecheck, and tests before release-oriented work.
8
+ - Keep CI no-publication by default.
9
+ - Keep package payloads free of local state, evidence, reference archives, fixtures, dependency folders, and package archives.
10
+ - Keep guarded vocabulary exceptions exact, reviewed, and removal-conditioned.
11
+
12
+ ## OpenCode Surfaces
13
+
14
+ - npm script: `scan:legacy-tokens`
15
+ - npm script: `check:version`
16
+ - npm script: `check:pack-payload`
17
+ - Runtime feature id: `guardrails`
18
+
19
+ ## Safety
20
+
21
+ - This file is static documentation.
22
+ - Do not execute commands from this file automatically.
23
+ - Do not publish, push refs, create tags, or add auth tokens without explicit authorization.
@@ -0,0 +1,22 @@
1
+ # Tool Guards
2
+
3
+ Use this LitOpenCode skill when a contributor needs the static tool-guard map for OpenCode hook behavior.
4
+
5
+ ## Covers
6
+
7
+ - Inspect tool execution before and after supported tool calls.
8
+ - Allow unrelated tool calls to pass through unchanged.
9
+ - Deny unsafe guarded tool requests fail-closed.
10
+ - Add structured metadata after supported tool calls complete.
11
+
12
+ ## OpenCode Surfaces
13
+
14
+ - Hook: `tool.execute.before`
15
+ - Hook: `tool.execute.after`
16
+ - Runtime feature area: tool guards
17
+
18
+ ## Safety
19
+
20
+ - This file is static documentation.
21
+ - Do not execute commands from this file automatically.
22
+ - Keep guard decisions explicit and auditable.
@@ -0,0 +1,24 @@
1
+ # Workflow Loop
2
+
3
+ Use this LitOpenCode skill when a contributor needs the static workflow-loop map for `planning-start-work-loop`.
4
+
5
+ ## Covers
6
+
7
+ - Start with a concrete plan before implementation work begins.
8
+ - Keep implementation slices scoped to the active task.
9
+ - Record observable progress through evidence and durable ledger entries.
10
+ - Pair worker changes with independent verification before completion claims.
11
+
12
+ ## OpenCode Surfaces
13
+
14
+ - Command: `/litwork`
15
+ - Primary agents: `lit-plan`, `lit-loop`
16
+ - Recommended role aliases: `lit-architect`, `lit-forge`, `lit-oracle`, `lit-prover`, `lit-sentinel`, `lit-librarian`
17
+ - Runtime feature id: `planning-start-work-loop`
18
+ - Runtime feature id: `lit-litwork-activation`
19
+
20
+ ## Safety
21
+
22
+ - This file is static documentation.
23
+ - Do not execute commands from this file automatically.
24
+ - Keep project progress in durable state rather than chat-only notes.
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import process from "node:process";
5
+
6
+ const FORBIDDEN_PATH_RULES = [
7
+ { label: "original prompt provenance", pattern: /^INITIAL_PROMPT\.md$/u },
8
+ { label: "handoff state document", pattern: /^HANDOFF\.md$/u },
9
+ { label: "repository automation", pattern: /(^|\/)\.github(\/|$)/u },
10
+ { label: "internal recon/spec document", pattern: /^docs\/(?:recon|spec)(\/|$)/u },
11
+ { label: "implementation plan", pattern: /(^|\/)plans(\/|$)/u },
12
+ { label: ".litcodex runtime state", pattern: /(^|\/)\.litcodex(\/|$)/u },
13
+ { label: ".litopencode runtime state", pattern: /(^|\/)\.litopencode(\/|$)/u },
14
+ { label: "evidence artifact", pattern: /(^|\/)evidence(\/|$)/u },
15
+ { label: "test file", pattern: /(^|\/)tests?(\/|$)/u },
16
+ { label: "reference archive", pattern: /(^|\/)# REFERENCE(\/|$)/u },
17
+ { label: "package archive", pattern: /\.tgz$/u },
18
+ { label: "dependency directory", pattern: /(^|\/)node_modules(\/|$)/u },
19
+ { label: "git directory", pattern: /(^|\/)\.git(\/|$)/u },
20
+ { label: "local scanner allowlist", pattern: /^tools\/legacy-token-allowlist\.json$/u }
21
+ ];
22
+
23
+ class PackPayloadError extends Error {
24
+ constructor(message, exitCode) {
25
+ super(message);
26
+ this.name = "PackPayloadError";
27
+ this.exitCode = exitCode;
28
+ }
29
+ }
30
+
31
+ function parseArgs(argv) {
32
+ const options = {
33
+ mode: "stdin",
34
+ filePath: undefined
35
+ };
36
+
37
+ for (let index = 0; index < argv.length; index += 1) {
38
+ const arg = argv[index];
39
+ const next = argv[index + 1];
40
+ if (arg === "--stdin") {
41
+ options.mode = "stdin";
42
+ } else if (arg === "--file" && next !== undefined) {
43
+ options.mode = "file";
44
+ options.filePath = next;
45
+ index += 1;
46
+ } else {
47
+ throw new PackPayloadError(`unknown or incomplete argument: ${arg}`, 2);
48
+ }
49
+ }
50
+
51
+ return options;
52
+ }
53
+
54
+ async function readStdin() {
55
+ const chunks = [];
56
+ for await (const chunk of process.stdin) {
57
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
58
+ }
59
+ return Buffer.concat(chunks).toString("utf8");
60
+ }
61
+
62
+ async function readManifestText(options) {
63
+ if (options.mode === "file") {
64
+ return fs.readFile(path.resolve(options.filePath), "utf8");
65
+ }
66
+ return readStdin();
67
+ }
68
+
69
+ function parseManifest(text) {
70
+ try {
71
+ return JSON.parse(text);
72
+ } catch (error) {
73
+ const detail = error instanceof Error ? error.message : String(error);
74
+ throw new PackPayloadError(`cannot read or parse manifest: ${detail}`, 2);
75
+ }
76
+ }
77
+
78
+ function requireObject(value, label) {
79
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
80
+ throw new PackPayloadError(`${label} must be a JSON object`, 2);
81
+ }
82
+ return value;
83
+ }
84
+
85
+ function normalizeManifestPath(value) {
86
+ return value.split(path.sep).join("/");
87
+ }
88
+
89
+ function readManifestFiles(parsed) {
90
+ if (!Array.isArray(parsed) || parsed.length !== 1) {
91
+ throw new PackPayloadError("manifest must be the one-package array emitted by npm pack --dry-run --json", 2);
92
+ }
93
+
94
+ const packageEntry = requireObject(parsed[0], "manifest package");
95
+ const files = packageEntry.files;
96
+ if (!Array.isArray(files)) {
97
+ throw new PackPayloadError("manifest package files must be an array", 2);
98
+ }
99
+
100
+ return files.map((entry, index) => {
101
+ const fileEntry = requireObject(entry, `manifest package files[${index}]`);
102
+ if (typeof fileEntry.path !== "string" || fileEntry.path.trim() === "") {
103
+ throw new PackPayloadError(`manifest package files[${index}].path must be a non-empty string`, 2);
104
+ }
105
+ return normalizeManifestPath(fileEntry.path);
106
+ });
107
+ }
108
+
109
+ function findForbiddenPaths(paths) {
110
+ const matches = [];
111
+ for (const manifestPath of paths) {
112
+ const rule = FORBIDDEN_PATH_RULES.find((candidate) => candidate.pattern.test(manifestPath));
113
+ if (rule !== undefined) {
114
+ matches.push({ path: manifestPath, reason: rule.label });
115
+ }
116
+ }
117
+ return matches;
118
+ }
119
+
120
+ function printForbiddenPaths(matches) {
121
+ for (const match of matches) {
122
+ console.log(`${match.path} :: ${match.reason}`);
123
+ }
124
+ }
125
+
126
+ async function main() {
127
+ try {
128
+ const options = parseArgs(process.argv.slice(2));
129
+ const manifestText = await readManifestText(options);
130
+ const manifest = parseManifest(manifestText);
131
+ const files = readManifestFiles(manifest);
132
+ const forbidden = findForbiddenPaths(files);
133
+
134
+ if (forbidden.length > 0) {
135
+ const suffix = forbidden.length === 1 ? "path" : "paths";
136
+ console.log(`pack payload guard failed: ${forbidden.length} forbidden ${suffix}`);
137
+ printForbiddenPaths(forbidden);
138
+ process.exitCode = 1;
139
+ return;
140
+ }
141
+
142
+ const suffix = files.length === 1 ? "file" : "files";
143
+ console.log(`pack payload guard passed: ${files.length} ${suffix} checked`);
144
+ } catch (error) {
145
+ if (error instanceof PackPayloadError) {
146
+ console.error(`pack payload guard error: ${error.message}`);
147
+ process.exitCode = error.exitCode;
148
+ return;
149
+ }
150
+ const message = error instanceof Error ? error.message : String(error);
151
+ console.error(`pack payload guard error: ${message}`);
152
+ process.exitCode = 2;
153
+ }
154
+ }
155
+
156
+ await main();