orbit-thread 0.0.0-stage → 1.1.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.
@@ -0,0 +1,179 @@
1
+ # Release validation record
2
+
3
+ Checks performed on 5 October 2026, using disposable Git repositories.
4
+
5
+ ## Original v1.0.0 checks
6
+
7
+ ### Automated tests
8
+
9
+ All 26 tests passed locally on macOS with Node.js 24. Tests cover first creation,
10
+ overwrite, exactly 50 lines, 51-line rejection, final lines with and without a
11
+ newline, empty and synthetic secret/private-key rejection, preservation of the
12
+ old file, paths with spaces, cleanup after validation and rename failures, and
13
+ unchanged Git index/refs with network-command guards.
14
+
15
+ CLI tests cover Codex, Claude and both-agent project installs, identical copies,
16
+ reinstall, check, update across package versions, uninstall, user-scope locations
17
+ in a disposable home, init idempotency and block upgrades, ignore rules, existing
18
+ instructions, edited files/blocks, unrelated files/directories, symlinks and
19
+ invalid ownership paths. No live user installation is altered by these tests.
20
+
21
+ CI ran the suite, local package validation, source scan, ZIP build and npm pack
22
+ on macOS/Linux with Node.js 20 and 24. All four jobs passed in the
23
+ [initial public CI run](https://github.com/shab1h-gh/orbit-handoff/actions/runs/37254076291).
24
+
25
+ ### Native discovery and packaging
26
+
27
+ - Codex CLI 0.160.0 returned the installed `handoff` from its native `skills/list`
28
+ API, with display name Orbit Handoff and a `$handoff` default prompt.
29
+ - Claude Code 2.1.289 passed `claude plugin validate ./plugin --strict` and
30
+ `claude plugin validate . --strict`.
31
+ - A local marketplace install succeeded. `claude plugin details orbit-handoff`
32
+ reported one `handoff` skill, zero agents/hooks/MCP/LSP servers.
33
+ - A second isolated configuration installed `orbit-handoff@orbit-handoff` from
34
+ `shab1h-gh/orbit-handoff` on GitHub and reported version 1.0.0 enabled.
35
+ - Claude startup's `slash_commands` and `skills` arrays contained
36
+ `orbit-handoff:handoff`; its plugin metadata reported version 1.0.0.
37
+ - A standalone Claude project install registered and loaded `handoff`, confirming
38
+ the `/handoff` command independently of the plugin namespace.
39
+ - The attempted Claude model run ended with **Not logged in**. Registration and
40
+ installation are verified; an authenticated agent-driven handoff is untested.
41
+ - The portable manifest passed the official Agent Plugins 1.0.0 JSON Schema
42
+ using Ajv's draft-2020 validator. Local checks cover OpenAI listing limits,
43
+ real PNG assets, skill metadata and the absence of MCP/hooks.
44
+ - `npm pack` was inspected and the packed CLI installed both agents through
45
+ `npx --package` in a clean disposable project.
46
+ - The interactive installer was exercised in a terminal: agent selection,
47
+ project scope selection, destination preview and confirmation all completed.
48
+ - Public-source scans passed. Archives are inspected separately for unwanted
49
+ paths, credentials, test output and build residue before release.
50
+
51
+ ## Publication limits
52
+
53
+ `orbit-handoff@1.0.0` was successfully published manually. Public registry
54
+ verification on 5 October 2026 confirmed version 1.0.0, `latest` resolving to
55
+ 1.0.0 before the patch release, and repository/homepage/issue metadata pointing
56
+ to `shab1h-gh/orbit-handoff`. The previous failed publication attempt is stale
57
+ history; the registry package is available.
58
+
59
+ Neither OpenAI nor Anthropic public directory submission has been made.
60
+ Provider review, identity verification and portal checks remain publisher steps.
61
+ Native Windows, WSL-specific operation and ordinary ChatGPT execution are
62
+ unverified; the supported runtime is macOS/Linux with a POSIX shell.
63
+
64
+ ## v1.0.1 patch verification
65
+
66
+ The patch changes documentation and release version metadata only. No installer,
67
+ canonical skill, writer, tests or packaging logic changed.
68
+
69
+ ### Public v1.0.0 smoke test
70
+
71
+ Downloaded the actual public package with an isolated npm cache and ran
72
+ `npx orbit-handoff@1.0.0 install --agent both --scope project --yes` in a disposable
73
+ Git repository on macOS with Node.js 24.16.0 and npm 12.0.2. Verified:
74
+
75
+ - Codex and Claude installs contain byte-identical skill files and ownership metadata.
76
+ - The npm CLI has executable permissions. Installed writers are mode 0644 and
77
+ execute through `sh`, matching the canonical skill's invocation contract.
78
+ - `check` succeeds after install; `init` preserves existing AGENTS.md, CLAUDE.md
79
+ and .gitignore content, records ownership and ignores HANDOFF-STATE.md.
80
+ - Reinstall and repeated init preserve bytes and add no duplicate blocks.
81
+ - Init creates no handoff. The writer creates and atomically replaces the file
82
+ with restrictive permissions; empty and 51-line inputs preserve the valid file.
83
+ - No writer temporary files remain. Uninstall restores original configuration,
84
+ removes unchanged owned skills, and preserves unrelated files and the handoff.
85
+ - Edited skills and continuity blocks survive uninstall; check and reinstall
86
+ report edited files with failure status rather than overwriting them.
87
+
88
+ This smoke test exercises the CLI and shell workflow. It does not claim a new
89
+ native agent discovery test or an authenticated agent-driven handoff.
90
+
91
+ ### Local checks and packed v1.0.1 smoke test
92
+
93
+ - All 26 existing tests passed on macOS with Node.js 24.16.0.
94
+ - `npm run validate`, `npm run scan:public` and `npm run build:plugin` passed.
95
+ - Claude Code 2.1.289 passed both `claude plugin validate ./plugin --strict` and
96
+ `claude plugin validate . --strict` for the updated manifests.
97
+ - The portable manifest passed the official Agent Plugins 1.0.0 JSON Schema
98
+ with Ajv's draft-2020 validator and format checks.
99
+ - `npm pack --dry-run` and `npm pack` succeeded using a disposable cache.
100
+ The tarball contains 20 intended files, an executable CLI, version 1.0.1 and
101
+ the corrected README. No dependencies, credentials or build residue are included.
102
+ - The submission ZIP passed its integrity check and both extracted archives
103
+ passed the public/privacy scanner. No private paths or unwanted files were found.
104
+ - The packed 1.0.1 CLI passed the same disposable-repository install, check,
105
+ init, reinstall, handoff and uninstall smoke test described above, including
106
+ edited-content preservation and executable permissions.
107
+ - Complete diff review confirmed that runtime code and canonical skill bytes
108
+ are unchanged. The v1.0.0 tag and release remain untouched.
109
+
110
+ These are pre-publication checks. After publication, verify `latest` resolves to
111
+ 1.0.1 and repeat the public-registry smoke test; report its result in the GitHub
112
+ release. The original CI result above covers v1.0.0; it is not a new 1.0.1 CI claim.
113
+
114
+
115
+ ## v1.1.0 feature validation
116
+
117
+ Validation completed on 7 October 2026 for the Orbit Thread feature release.
118
+
119
+ ### Verified in GitHub CI
120
+
121
+ The final feature-branch workflow passed on macOS and Linux with Node.js 20 and 24.
122
+
123
+ The workflow verified:
124
+
125
+ - the complete Node test suite;
126
+ - package/manifest validation;
127
+ - public/privacy source scanning;
128
+ - deterministic OpenAI/plugin ZIP generation;
129
+ - `npm pack`;
130
+ - a packed-tarball smoke test in a disposable Git repository.
131
+
132
+ The packed smoke test exercised the actual `orbit-thread-1.1.0.tgz` through:
133
+
134
+ - install for Codex and Claude;
135
+ - setup;
136
+ - Claude and Codex subagent configuration;
137
+ - doctor;
138
+ - update;
139
+ - uninstall;
140
+ - preservation of living project docs/config after uninstall.
141
+
142
+ It also verified both canonical skills, the expected living-document templates, `.orbit-thread/config.json`, and that setup does not create `HANDOFF-STATE.md`.
143
+
144
+ ### Behaviour verified by tests
145
+
146
+ Coverage includes:
147
+
148
+ - Orbit Setup creation/idempotency and preservation of existing project docs/instructions;
149
+ - selective project state and Git-ignore management;
150
+ - Orbit Handoff → Orbit Thread project-state migration;
151
+ - managed skill ownership/update/uninstall safeguards;
152
+ - subagent model/reasoning configuration and AGENTS.md reflection;
153
+ - doctor detection of duplicate root/docs living documents;
154
+ - Handoff atomic overwrite, 50-line limit, secret-pattern rejection, failure preservation and no Git/network mutation.
155
+
156
+ ### Invocation policy
157
+
158
+ The canonical Handoff skill intentionally omits Claude's former
159
+ `disable-model-invocation: true` restriction and keeps OpenAI metadata
160
+ `allow_implicit_invocation: true`.
161
+
162
+ Configured Orbit Thread projects therefore permit proactive checkpoints after meaningful verified milestones and completed coding tasks, while manual `$handoff` / `/handoff` invocation remains supported.
163
+
164
+ Orbit Setup remains separately exposed as `$orbit-setup` / `/orbit-setup`.
165
+
166
+ ### Remaining release-gate checks
167
+
168
+ Before publishing/tagging v1.1.0:
169
+
170
+ - validate the final plugin with a current Claude Code installation using `claude plugin validate`;
171
+ - verify native Claude and Codex skill discovery against the final package where those CLIs are available;
172
+ - publish `orbit-thread@1.1.0` and repeat the public-registry smoke test.
173
+
174
+ Those native CLIs are not part of GitHub CI, so CI success does not claim those checks.
175
+
176
+ ### Provider directories
177
+
178
+ OpenAI and Anthropic public-directory review is separate from npm/GitHub distribution. Their publisher dashboards are authoritative after submission; local validation does not imply approval.
179
+
package/lib/cli.mjs ADDED
@@ -0,0 +1,296 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import crypto from 'node:crypto';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { createInterface } from 'node:readline/promises';
7
+ import { setupProject, checkProject, removeProjectConfig, resolveProjectRoot } from '../plugin/skills/orbit-setup/scripts/setup_project.mjs';
8
+ import { configureProject, readProjectConfig } from '../plugin/skills/orbit-setup/scripts/project_config.mjs';
9
+
10
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
11
+ const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
12
+ const skillsRoot = path.join(packageRoot, 'plugin/skills');
13
+ const marker = '.orbit-thread.json';
14
+ const legacyMarker = '.orbit-handoff.json';
15
+ const agents = ['codex', 'claude'];
16
+ const skills = ['handoff', 'orbit-setup'];
17
+ const digest = data => crypto.createHash('sha256').update(data).digest('hex');
18
+ const exists = p => { try { fs.lstatSync(p); return true; } catch (e) { if (e.code === 'ENOENT') return false; throw e; } };
19
+
20
+ function safePath(p) {
21
+ let cursor = path.resolve(p);
22
+ while (true) {
23
+ if (exists(cursor) && fs.lstatSync(cursor).isSymbolicLink()) throw new Error(`Refusing symlink: ${cursor}`);
24
+ const parent = path.dirname(cursor);
25
+ if (parent === cursor) break;
26
+ cursor = parent;
27
+ }
28
+ }
29
+
30
+ function atomicWrite(p, content) {
31
+ safePath(p);
32
+ fs.mkdirSync(path.dirname(p), { recursive: true });
33
+ const temporary = path.join(path.dirname(p), `.orbit-thread-${crypto.randomUUID()}.tmp`);
34
+ try {
35
+ fs.writeFileSync(temporary, content, { flag: 'wx', mode: exists(p) ? fs.statSync(p).mode & 0o777 : 0o644 });
36
+ fs.renameSync(temporary, p);
37
+ } finally { if (exists(temporary)) fs.unlinkSync(temporary); }
38
+ }
39
+
40
+ function files(dir, prefix = '') {
41
+ return fs.readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
42
+ const relative = path.posix.join(prefix, entry.name);
43
+ if (entry.isSymbolicLink()) throw new Error(`Refusing symlink: ${relative}`);
44
+ return entry.isDirectory() ? files(path.join(dir, entry.name), relative) : [relative];
45
+ }).sort();
46
+ }
47
+
48
+ function skillSource(skill) { return path.join(skillsRoot, skill); }
49
+ function bundled(skill) { return Object.fromEntries(files(skillSource(skill)).map(name => [name, fs.readFileSync(path.join(skillSource(skill), name))])); }
50
+ function agentSkillsRoot(agent, scope, root) { return path.join(scope === 'user' ? os.homedir() : root, agent === 'codex' ? '.agents/skills' : '.claude/skills'); }
51
+ function location(agent, scope, root, skill) { return path.join(agentSkillsRoot(agent, scope, root), skill); }
52
+
53
+ function ownershipPath(dir) {
54
+ safePath(dir);
55
+ const current = path.join(dir, marker);
56
+ const legacy = path.join(dir, legacyMarker);
57
+ if (exists(current) && exists(legacy)) throw new Error(`Both Orbit Thread and legacy Orbit Handoff ownership files exist at ${dir}; reconcile them before continuing.`);
58
+ if (exists(current)) return current;
59
+ if (exists(legacy)) return legacy;
60
+ return null;
61
+ }
62
+
63
+ function readOwnership(dir) {
64
+ const p = ownershipPath(dir);
65
+ if (!p) return null;
66
+ const state = JSON.parse(fs.readFileSync(p, 'utf8'));
67
+ if (!['orbit-handoff', 'orbit-thread'].includes(state.product) || state.schema !== 1 || typeof state.version !== 'string' || !state.files || typeof state.files !== 'object' || Array.isArray(state.files)) throw new Error(`Invalid ownership file: ${p}`);
68
+ for (const [name, hash] of Object.entries(state.files)) {
69
+ if (!name || name === marker || name === legacyMarker || path.posix.isAbsolute(name) || name.includes('\\') || name.split('/').some(part => ['.', '..', ''].includes(part)) || !/^[a-f0-9]{64}$/.test(hash)) throw new Error(`Invalid ownership entry: ${p}`);
70
+ }
71
+ return state;
72
+ }
73
+
74
+ function preflight(dir, skill, updating) {
75
+ const state = readOwnership(dir);
76
+ if (exists(dir) && !state) throw new Error(`An unmanaged ${skill} skill already exists at ${dir}; move it aside yourself before installing.`);
77
+ if (updating && !state) return true;
78
+ if (state) {
79
+ for (const [name, hash] of Object.entries(state.files)) {
80
+ const p = path.join(dir, name); safePath(p);
81
+ if (!exists(p) || digest(fs.readFileSync(p)) !== hash) throw new Error(`Managed file was edited or removed: ${p}. Preserve your changes before updating.`);
82
+ }
83
+ for (const name of Object.keys(bundled(skill))) if (exists(path.join(dir, name)) && !Object.hasOwn(state.files, name)) throw new Error(`Unrelated file conflicts with this release: ${path.join(dir, name)}`);
84
+ }
85
+ return true;
86
+ }
87
+
88
+ function installSkill(dir, skill) {
89
+ const previousPath = ownershipPath(dir);
90
+ const previous = readOwnership(dir);
91
+ const content = bundled(skill);
92
+ for (const [name, data] of Object.entries(content)) atomicWrite(path.join(dir, name), data);
93
+ for (const name of Object.keys(previous?.files ?? {})) if (!Object.hasOwn(content, name) && exists(path.join(dir, name))) fs.unlinkSync(path.join(dir, name));
94
+ atomicWrite(path.join(dir, marker), JSON.stringify({ product: 'orbit-thread', schema: 1, version: pkg.version, skill,
95
+ files: Object.fromEntries(Object.entries(content).map(([name, data]) => [name, digest(data)])) }, null, 2) + '\n');
96
+ if (previousPath && previousPath.endsWith(legacyMarker) && exists(previousPath)) fs.unlinkSync(previousPath);
97
+ }
98
+
99
+ function prune(dir, boundary) {
100
+ while (dir !== boundary && exists(dir) && fs.readdirSync(dir).length === 0) { fs.rmdirSync(dir); dir = path.dirname(dir); }
101
+ }
102
+
103
+ function uninstallSkill(dir) {
104
+ const ownerFile = ownershipPath(dir);
105
+ const state = readOwnership(dir);
106
+ if (!state) { if (exists(dir)) console.log(`Unmanaged skill preserved: ${dir}`); return; }
107
+ const retained = {};
108
+ for (const [name, hash] of Object.entries(state.files)) {
109
+ const p = path.join(dir, name); safePath(p);
110
+ if (!exists(p)) continue;
111
+ if (digest(fs.readFileSync(p)) === hash) { fs.unlinkSync(p); prune(path.dirname(p), dir); }
112
+ else { retained[name] = hash; console.log(`Preserved edited file: ${p}`); }
113
+ }
114
+ if (Object.keys(retained).length) atomicWrite(ownerFile, JSON.stringify({ ...state, files: retained }, null, 2) + '\n');
115
+ else if (ownerFile && exists(ownerFile)) fs.unlinkSync(ownerFile);
116
+ prune(dir, path.dirname(dir));
117
+ }
118
+
119
+ function skillStatus(root, selected, scope) {
120
+ let complete = true;
121
+ for (const agent of selected) {
122
+ for (const skill of skills) {
123
+ const dir = location(agent, scope, root, skill);
124
+ const state = readOwnership(dir);
125
+ let label = exists(dir) ? 'unmanaged skill' : 'not installed';
126
+ if (state) {
127
+ const contents = bundled(skill);
128
+ const intact = Object.entries(state.files).every(([name, hash]) => { const p = path.join(dir, name); return exists(p) && digest(fs.readFileSync(p)) === hash; });
129
+ const current = intact && Object.keys(state.files).length === Object.keys(contents).length && Object.entries(contents).every(([name, data]) => state.files[name] === digest(data));
130
+ label = !intact ? `v${state.version}, locally modified` : current ? `v${state.version}, current` : `v${state.version}, update available in this package`;
131
+ if (!current) complete = false;
132
+ } else complete = false;
133
+ console.log(`${agent}/${skill} (${scope}): ${label} — ${dir}`);
134
+ }
135
+ }
136
+ return complete;
137
+ }
138
+
139
+ const help = `Orbit Thread ${pkg.version}
140
+
141
+ Usage: orbit-thread <command> [options]
142
+
143
+ Commands:
144
+ install Install the Orbit Thread skills.
145
+ setup Prepare/upgrade the current project workflow and living docs.
146
+ configure Set project subagent preferences.
147
+ doctor Check installed skills and project continuity health.
148
+ update Upgrade unchanged managed skill files to this package version.
149
+ uninstall Remove managed skills and instruction blocks.
150
+ init Alias for setup.
151
+ check Check managed skill installation (legacy-compatible).
152
+
153
+ Common options:
154
+ --agent codex|claude|both
155
+ --scope project|user
156
+ --yes, -y
157
+
158
+ Configure options:
159
+ --model, --subagent-model <name>
160
+ --reasoning, --effort <level>
161
+ --max-subagents 0|1|2
162
+ --reset-subagents
163
+ --show
164
+
165
+ Examples:
166
+ npx orbit-thread install
167
+ npx orbit-thread setup
168
+ npx orbit-thread configure --agent claude --model "Claude Sonnet 5.5" --effort low
169
+ npx orbit-thread configure --agent codex --model "GPT-6.1 Sol" --reasoning high
170
+ npx orbit-thread doctor
171
+ npx orbit-thread@latest update --agent both --scope project --yes
172
+ `;
173
+
174
+ function parseOptions(rest) {
175
+ const options = { yes: false, show: false, resetSubagents: false };
176
+ for (let i = 0; i < rest.length; i++) {
177
+ const flag = rest[i];
178
+ const take = () => {
179
+ const value = rest[++i];
180
+ if (value === undefined) throw new Error(`Missing value for ${flag}`);
181
+ return value;
182
+ };
183
+ if (flag === '--yes' || flag === '-y') options.yes = true;
184
+ else if (flag === '--agent') options.agent = take();
185
+ else if (flag === '--scope') options.scope = take();
186
+ else if (flag === '--model' || flag === '--subagent-model') options.model = take();
187
+ else if (flag === '--reasoning' || flag === '--effort') options.reasoning = take();
188
+ else if (flag === '--max-subagents') options.maxSubagents = take();
189
+ else if (flag === '--reset-subagents') options.resetSubagents = true;
190
+ else if (flag === '--show') options.show = true;
191
+ else throw new Error(`Unknown option: ${flag}`);
192
+ }
193
+ return options;
194
+ }
195
+
196
+ function selectedAgents(value = 'both') {
197
+ if (!['codex', 'claude', 'both'].includes(value)) throw new Error('Agent must be codex, claude or both');
198
+ return value === 'both' ? agents : [value];
199
+ }
200
+
201
+ export async function main(argv) {
202
+ if (argv.includes('--help') || argv.includes('-h') || !argv.length) { console.log(help); return; }
203
+ if (argv.includes('--version') || argv.includes('-v')) { console.log(pkg.version); return; }
204
+
205
+ let [command, ...rest] = argv;
206
+ if (command === 'init') command = 'setup';
207
+ if (!['install', 'setup', 'configure', 'check', 'doctor', 'update', 'uninstall'].includes(command)) throw new Error(`Unknown command: ${command}`);
208
+
209
+ const options = parseOptions(rest);
210
+ const scope = options.scope ?? 'project';
211
+ if (!['project', 'user'].includes(scope)) throw new Error('Scope must be project or user');
212
+ if (['setup', 'configure'].includes(command) && scope !== 'project') throw new Error(`${command} configures the current project; use --scope project`);
213
+ if (process.platform === 'win32') throw new Error('Use a Linux environment in WSL; native Windows is not supported');
214
+
215
+ let selected = selectedAgents(options.agent);
216
+ const root = resolveProjectRoot(process.cwd());
217
+
218
+ if (command === 'configure') {
219
+ if (options.show) { console.log(JSON.stringify(readProjectConfig(root), null, 2)); return; }
220
+ if ((options.model !== undefined || options.reasoning !== undefined) && selected.length !== 1) {
221
+ throw new Error('Choose exactly one --agent when setting a subagent model or reasoning/effort preference.');
222
+ }
223
+ const config = configureProject(root, {
224
+ agent: selected.length === 1 ? selected[0] : undefined,
225
+ maxSubagents: options.maxSubagents,
226
+ model: options.model,
227
+ reasoning: options.reasoning,
228
+ resetSubagents: options.resetSubagents,
229
+ });
230
+ console.log(`Orbit Thread: project subagent configuration updated (max ${config.subagents.max}).`);
231
+ return;
232
+ }
233
+
234
+ if (command === 'check') {
235
+ if (!skillStatus(root, selected, scope)) process.exitCode = 1;
236
+ return;
237
+ }
238
+
239
+ if (command === 'doctor') {
240
+ const installed = skillStatus(root, selected, scope);
241
+ let healthy = installed;
242
+ if (scope === 'project') {
243
+ const result = checkProject(root, selected);
244
+ result.messages.forEach(message => console.log(message));
245
+ healthy = healthy && result.ok;
246
+ }
247
+ if (!healthy) process.exitCode = 1;
248
+ return;
249
+ }
250
+
251
+ const interactive = !options.yes;
252
+ if (interactive && !process.stdin.isTTY) throw new Error('Non-interactive input: pass --yes (and optionally --agent / --scope)');
253
+ const rl = interactive ? createInterface({ input: process.stdin, output: process.stdout }) : null;
254
+ try {
255
+ let effectiveScope = scope;
256
+ if (!options.agent && rl) {
257
+ const answer = (await rl.question('Choose agent: 1) Codex 2) Claude Code 3) Both [3]: ')).trim() || '3';
258
+ const chosen = { '1':'codex', '2':'claude', '3':'both' }[answer];
259
+ if (!chosen) throw new Error('Choose 1, 2 or 3');
260
+ selected = selectedAgents(chosen);
261
+ }
262
+ if (!options.scope && rl && command !== 'setup') {
263
+ const answer = (await rl.question('Choose scope: 1) Current project 2) User/global [1]: ')).trim() || '1';
264
+ effectiveScope = { '1':'project', '2':'user' }[answer];
265
+ if (!effectiveScope) throw new Error('Choose 1 or 2');
266
+ }
267
+
268
+ if (command === 'setup') {
269
+ console.log(`Will configure Orbit Thread project continuity in ${root}. Existing docs and unrelated instructions are preserved.`);
270
+ if (rl && !['y','yes'].includes((await rl.question('Continue? [y/N]: ')).trim().toLowerCase())) { console.log('Cancelled.'); return; }
271
+ const result = setupProject(root, selected);
272
+ for (const item of [...result.managed, ...result.docs]) console.log(`${item.action}: ${item.name}`);
273
+ console.log('Orbit Thread: setup complete. HANDOFF-STATE.md will be created at the first meaningful checkpoint.');
274
+ return;
275
+ }
276
+
277
+ const targets = selected.flatMap(a => skills.map(skill => ({ agent:a, skill, dir:location(a, effectiveScope, root, skill) })));
278
+ let eligible;
279
+ if (command === 'install' || command === 'update') {
280
+ if (command === 'update' && !targets.some(t => readOwnership(t.dir))) throw new Error('No managed Orbit Thread/legacy Orbit Handoff installation found; run install first');
281
+ eligible = targets.map(t => preflight(t.dir, t.skill, command === 'update'));
282
+ } else targets.forEach(t => readOwnership(t.dir));
283
+
284
+ console.log(`Will ${command} Orbit Thread ${pkg.version} (${effectiveScope}):\n${targets.map(t => t.dir).join('\n')}`);
285
+ if (rl && !['y','yes'].includes((await rl.question('Continue? [y/N]: ')).trim().toLowerCase())) { console.log('Cancelled.'); return; }
286
+
287
+ if (command === 'uninstall') {
288
+ targets.forEach(t => uninstallSkill(t.dir));
289
+ if (effectiveScope === 'project') removeProjectConfig(root, selected);
290
+ } else {
291
+ targets.forEach((t, i) => { if (command === 'install' || eligible[i]) installSkill(t.dir, t.skill); });
292
+ }
293
+ const suffix = command === 'install' && effectiveScope === 'project' ? '\nRun `npx orbit-thread setup` to prepare this project.' : '';
294
+ console.log(`Orbit Thread: ${command} complete.${suffix}`);
295
+ } finally { rl?.close(); }
296
+ }
package/package.json CHANGED
@@ -1,6 +1,27 @@
1
1
  {
2
2
  "name": "orbit-thread",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.1.0",
4
+ "description": "Token-efficient project setup, checkpoints and coding-session continuity for Codex and Claude Code.",
5
+ "type": "module",
6
+ "bin": {
7
+ "orbit-thread": "bin/orbit-thread.mjs",
8
+ "orbit-handoff": "bin/orbit-thread.mjs"
9
+ },
10
+ "engines": { "node": ">=20" },
11
+ "os": ["darwin", "linux"],
12
+ "license": "MIT",
13
+ "author": "Shabih Anwar",
14
+ "repository": { "type": "git", "url": "git+https://github.com/shab1h-gh/orbit-thread.git" },
15
+ "homepage": "https://github.com/shab1h-gh/orbit-thread#readme",
16
+ "bugs": { "url": "https://github.com/shab1h-gh/orbit-thread/issues" },
17
+ "keywords": ["codex", "claude-code", "agent-skills", "continuity", "project-setup", "handoff", "context-efficiency"],
18
+ "files": ["bin/", "lib/", "plugin/", "docs/", "README.md", "LICENSE", "SECURITY.md", "PRIVACY.md", "CONTRIBUTING.md", "CHANGELOG.md"],
19
+ "scripts": {
20
+ "test": "node --test",
21
+ "validate": "node scripts/validate.mjs",
22
+ "build:plugin": "python3 scripts/package_plugin.py",
23
+ "scan:public": "node scripts/scan-public.mjs",
24
+ "smoke:pack": "node scripts/smoke-pack.mjs"
25
+ },
26
+ "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" }
27
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "orbit-thread",
3
+ "displayName": "Orbit Thread",
4
+ "version": "1.1.0",
5
+ "description": "Token-efficient project setup, checkpoints and coding-session continuity.",
6
+ "author": { "name": "Shabih Anwar", "url": "https://github.com/shab1h-gh" },
7
+ "homepage": "https://github.com/shab1h-gh/orbit-thread",
8
+ "repository": "https://github.com/shab1h-gh/orbit-thread",
9
+ "license": "MIT",
10
+ "icon": "./assets/icon.png",
11
+ "documentationUrl": "https://github.com/shab1h-gh/orbit-thread#readme",
12
+ "supportUrl": "https://github.com/shab1h-gh/orbit-thread/issues",
13
+ "privacyPolicyUrl": "https://github.com/shab1h-gh/orbit-thread/blob/main/PRIVACY.md",
14
+ "keywords": ["continuity", "handoff", "project-setup", "developer-tools"],
15
+ "skills": "./skills/"
16
+ }
package/plugin/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shabih Anwar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,26 @@
1
+ # Orbit Thread
2
+
3
+ Orbit Thread provides two local coding skills:
4
+
5
+ - **Orbit Setup** prepares a project with lightweight living docs, selective context rules and configurable subagent preferences.
6
+ - **Handoff** overwrites a compact, evidence-only `HANDOFF-STATE.md` so a fresh coding session can resume from Git + current project truth rather than a large old conversation.
7
+
8
+ Standalone commands:
9
+
10
+ - Codex: `$orbit-setup` and `$handoff`
11
+ - Claude Code: `/orbit-setup` and `/handoff`
12
+
13
+ Claude plugin commands:
14
+
15
+ - `/orbit-thread:orbit-setup`
16
+ - `/orbit-thread:handoff`
17
+
18
+ Setup creates only missing PRODUCT, DESIGN, ARCHITECTURE, SECURITY and ROADMAP templates, preserves existing equivalents, manages a concise Orbit Thread section in AGENTS.md/CLAUDE.md, and keeps local handoff/tool state Git-ignored.
19
+
20
+ The project rules update living docs in place, load only relevant context, default to no subagents with a maximum of two, and request Handoff after meaningful verified milestones/completed coding tasks.
21
+
22
+ Handoff is overwrite-only, capped at 50 lines, secret-checked before atomic replacement and makes no network calls or Git mutations.
23
+
24
+ There is no MCP server, telemetry or external account.
25
+
26
+ See the project README for installation, migration and update instructions.
Binary file
@@ -0,0 +1,34 @@
1
+ {
2
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
+ "name": "orbit-thread",
4
+ "version": "1.1.0",
5
+ "description": "Token-efficient project setup, checkpoints and coding-session continuity.",
6
+ "author": { "name": "Shabih Anwar", "url": "https://github.com/shab1h-gh" },
7
+ "homepage": "https://github.com/shab1h-gh/orbit-thread",
8
+ "repository": "https://github.com/shab1h-gh/orbit-thread",
9
+ "license": "MIT",
10
+ "keywords": ["handoff", "codex", "claude-code", "continuity", "project-setup", "developer-tools"],
11
+ "extensions": {
12
+ "com.openai": {
13
+ "interface": {
14
+ "displayName": "Orbit Thread",
15
+ "shortDescription": "Lean coding continuity",
16
+ "longDescription": "Orbit Thread gives coding agents a small, durable continuity layer. Orbit Setup creates only missing PRODUCT, DESIGN, ARCHITECTURE, SECURITY and ROADMAP living docs, adds selective context-loading and bounded-subagent rules, and keeps local execution state separate from tracked project truth. Handoff then overwrites a compact HANDOFF-STATE.md at meaningful verified checkpoints and completed coding tasks so a fresh session can resume from Git, current docs and the exact next action instead of carrying a large chat history. Handoffs are capped at 50 lines, validated before atomic replacement, and the writer makes no network calls or Git mutations. Existing project docs and unrelated instructions are preserved.",
17
+ "developerName": "Shabih Anwar",
18
+ "category": "Developer Tools",
19
+ "capabilities": ["Prepare project continuity", "Save verified coding checkpoints"],
20
+ "websiteURL": "https://github.com/shab1h-gh/orbit-thread",
21
+ "supportURL": "https://github.com/shab1h-gh/orbit-thread/issues",
22
+ "privacyPolicyURL": "https://github.com/shab1h-gh/orbit-thread/blob/main/PRIVACY.md",
23
+ "defaultPrompt": ["Prepare this coding project for efficient continuity, or save verified current state at a meaningful checkpoint."],
24
+ "brandColor": "#4338CA",
25
+ "composerIcon": "./assets/icon.png",
26
+ "logo": "./assets/icon.png"
27
+ },
28
+ "onboardingSkill": "./skills/orbit-setup/SKILL.md",
29
+ "publication": {
30
+ "release_notes": "Orbit Thread v1.1 adds project setup, living docs, configurable subagent preferences, automatic meaningful checkpoints, fresh-context recovery and Orbit Handoff migration support."
31
+ }
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: handoff
3
+ description: Use Orbit Thread to overwrite HANDOFF-STATE.md with a compact, secret-free checkpoint of verified coding state. Invoke when the user asks for a handoff, when Orbit Thread project instructions require a meaningful checkpoint, before a fresh/cleared session, or before finalising a completed coding task whose repository state or durable project truth changed.
4
+ ---
5
+
6
+ # Orbit Thread Handoff
7
+
8
+ Write concise current operational state for the next session, not a changelog or transcript.
9
+
10
+ ## Workflow
11
+
12
+ 1. Gather state once: `date '+%d-%m-%Y %H:%M'; git status --short --branch; git diff --stat HEAD; git log -1 --oneline`. If Git is unavailable, record that fact and use only current-session evidence. Reuse a handoff already read this session; otherwise read it once. Consult only files changed or discussed for the current work.
13
+ 2. Reconcile previous state with current evidence. Keep only facts still true and work still relevant. Drop resolved, superseded or duplicated history. Repository state and relevant tracked docs override stale handoff content.
14
+ 3. Pipe the new Markdown to this skill's `scripts/write_handoff.sh`, resolving the script relative to the loaded skill directory. In Claude Code, `${CLAUDE_SKILL_DIR}/scripts/write_handoff.sh` resolves that location; in Codex, use the loaded skill directory. Quote the path and run it with `sh`.
15
+ 4. The writer overwrites repo-root `HANDOFF-STATE.md` atomically, rejects empty/over-50-line/obviously secret-bearing content, and prints the line count. Do not re-read after a successful write.
16
+ 5. If this is an intermediate checkpoint and the current request still has unfinished work, continue. If the task is complete, return the normal final response.
17
+
18
+ ## Format
19
+
20
+ ```markdown
21
+ # Handoff State
22
+
23
+ Updated: DD-MM-YYYY HH:MM
24
+
25
+ ## Current objective
26
+ ## Completed milestones
27
+ ## In progress
28
+ ## Current state
29
+ ## Decisions
30
+ ## Open issues / risks
31
+ ## Immediate next steps
32
+ ## Verification
33
+ ```
34
+
35
+ Omit empty sections. Aim for about 30 lines; hard cap 50. State each fact once. Immediate next steps must be genuinely outstanding.
36
+
37
+ ## Rules
38
+
39
+ - Checkpoint after meaningful verified milestones and completed coding tasks when the configured project rules require it; skip read-only questions and trivial edits.
40
+ - Use read-only Git inspection only. Never commit, stage, amend, push, reset or otherwise mutate Git.
41
+ - Evidence only. Never claim a test, deployment, commit, push or external action happened without evidence.
42
+ - Never include secrets, credentials, tokens, private keys, recovery codes, `.env`/`.dev.vars` values, personal data or sensitive production data.
43
+ - Do not record the current HEAD SHA; it becomes stale easily. Exact SHAs are allowed only for immutable earlier revisions such as releases, deployments or rollback points.
44
+ - `HANDOFF-STATE.md` is local current state, intended to be Git-ignored. Never append history or make committing it a next step.
45
+ - Durable product, design, architecture, security or roadmap truth belongs in tracked living docs. Handoff does not replace those files.
46
+ - At an intermediate checkpoint, do not stop merely because the handoff was written.