orizu 0.5.9 → 0.5.11

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 (60) hide show
  1. package/dist/daytona-slice-rehearsal.js +2 -2
  2. package/dist/daytona-slice.js +1 -51
  3. package/dist/git-credential.js +10 -7
  4. package/dist/github-setup.js +18 -2
  5. package/dist/help.js +16 -11
  6. package/dist/hosted-boot.js +713 -0
  7. package/dist/hosted-bootstrap.js +19 -41
  8. package/dist/hosted-harness-opencode.js +4 -2
  9. package/dist/hosted-headless.js +17 -6
  10. package/dist/hosted-loop-lifecycle.js +172 -0
  11. package/dist/hosted-loop.js +57 -148
  12. package/dist/hosted-run-event-sink.js +49 -10
  13. package/dist/hosted-runtime-assets.js +25 -1
  14. package/dist/hosted-session-cli.js +101 -8
  15. package/dist/hosted-skill-staging.js +89 -0
  16. package/dist/index.js +82 -62
  17. package/dist/manifests-cli.js +7 -2
  18. package/dist/preview-runtime.js +21 -4
  19. package/dist/prompt-draft-push.js +95 -0
  20. package/dist/report-comments-cli.js +9 -2
  21. package/dist/sandbox-hygiene.js +60 -0
  22. package/dist/sandbox-provider.js +15 -3
  23. package/dist/scorer-draft-push.js +110 -0
  24. package/dist/team-connectors-cli.js +140 -0
  25. package/dist/vercel-sandbox-provider.js +7 -1
  26. package/dist/workbench-cli.js +234 -6
  27. package/dist/workspace-sync.js +9 -2
  28. package/dist/workspace.js +15 -43
  29. package/dist/zip-draft-push.js +125 -0
  30. package/package.json +6 -6
  31. package/src/daytona-slice-rehearsal.ts +2 -2
  32. package/src/daytona-slice.ts +1 -70
  33. package/src/git-credential.ts +10 -7
  34. package/src/github-setup.ts +24 -2
  35. package/src/help.ts +18 -11
  36. package/src/hosted-boot.ts +946 -0
  37. package/src/hosted-bootstrap.ts +20 -44
  38. package/src/hosted-harness-opencode.ts +4 -2
  39. package/src/hosted-headless.ts +26 -8
  40. package/src/hosted-loop-lifecycle.ts +298 -0
  41. package/src/hosted-loop.ts +91 -269
  42. package/src/hosted-run-event-sink.ts +58 -11
  43. package/src/hosted-runtime-assets.ts +42 -1
  44. package/src/hosted-session-cli.ts +130 -8
  45. package/src/hosted-skill-staging.ts +126 -0
  46. package/src/index.ts +96 -72
  47. package/src/manifests-cli.ts +8 -2
  48. package/src/preview-runtime.ts +27 -4
  49. package/src/prompt-draft-push.ts +143 -0
  50. package/src/report-comments-cli.ts +10 -2
  51. package/src/sandbox-hygiene.ts +79 -0
  52. package/src/sandbox-provider.ts +15 -3
  53. package/src/scorer-draft-push.ts +155 -0
  54. package/src/team-connectors-cli.ts +171 -0
  55. package/src/vercel-sandbox-provider.ts +7 -1
  56. package/src/workbench-cli.ts +326 -6
  57. package/src/workspace-sync.ts +9 -2
  58. package/src/workspace.ts +15 -45
  59. package/src/zip-draft-push.ts +177 -0
  60. package/vendor/skills/orizu-cli/references/building-apps.md +2 -0
@@ -51,17 +51,17 @@ export function seedLocalBareRepo(baseDir, config) {
51
51
  const work = mkdtempSync(join(baseDir, 'seed-'));
52
52
  gitOrThrow(['init', '--initial-branch=main', work]);
53
53
  const ident = ['-c', 'user.email=seed@orizu.local', '-c', 'user.name=Seed'];
54
+ // ALI-1075: no `canonical`/`repoState` liveness fields — manifests carry
55
+ // machine-readable ids only; the DB label is the sole production pointer.
54
56
  const teamManifest = {
55
57
  schemaVersion: WORKSPACE_SCHEMA_VERSION,
56
58
  kind: 'team',
57
59
  slug: config.teamSlug,
58
- canonical: { owner: 'repo', repoState: 'source', objectRef: null },
59
60
  };
60
61
  const projectManifest = {
61
62
  schemaVersion: WORKSPACE_SCHEMA_VERSION,
62
63
  kind: 'project',
63
64
  slug: config.projectSlug,
64
- canonical: { owner: 'repo', repoState: 'draft', objectRef: null },
65
65
  };
66
66
  writeFileSync(join(work, 'orizu.team.json'), `${JSON.stringify(teamManifest, null, 2)}\n`);
67
67
  writeFileSync(join(work, '.gitignore'), '# Orizu workspace policy\n.orizu/\n');
@@ -23,6 +23,7 @@
23
23
  * branch → approve → apply via the API, then destroy the sandbox.
24
24
  */
25
25
  import { WORKSPACE_SCHEMA_VERSION } from './workspace.js';
26
+ import { sweepForTokenResidue } from './sandbox-hygiene.js';
26
27
  const REPO_DIR = 'repo';
27
28
  const DEFAULT_GENERATED_FILE = 'orizu-slice-note.md';
28
29
  const STEP_PLAN = [
@@ -118,57 +119,6 @@ async function mintRepoToken(fetcher, workspaceId, purpose, sessionId) {
118
119
  expiresAt: typeof body.expiresAt === 'string' ? body.expiresAt : null,
119
120
  };
120
121
  }
121
- /**
122
- * The list of places a short-lived git token can leak inside a sandbox after a
123
- * transient clone+push. List-driven so it is trivial to extend. `${repo}` is
124
- * substituted with the repo path. Ordered from the repo-local stores outward to
125
- * global config, the credential cache, the reflog, shell history, and the live
126
- * process listing / argv — the exact surfaces the Daytona-support question names.
127
- */
128
- export function tokenLeakProbes(repoPath) {
129
- const R = repoPath;
130
- return [
131
- { location: `${R}/.git/config`, command: `cat ${R}/.git/config 2>/dev/null || true` },
132
- { location: `${R}/.git/logs (reflog)`, command: `find ${R}/.git/logs -type f -exec cat {} + 2>/dev/null || true` },
133
- { location: '~/.git-credentials', command: 'cat "$HOME/.git-credentials" 2>/dev/null || true' },
134
- { location: '~/.gitconfig (global)', command: 'cat "$HOME/.gitconfig" 2>/dev/null || true' },
135
- { location: 'git credential cache/store', command: 'cat "$HOME/.cache/git/credential/"* "${XDG_CACHE_HOME:-$HOME/.cache}/git/credential/"* 2>/dev/null || true' },
136
- { location: 'shell history', command: 'cat "$HOME/.bash_history" "$HOME/.zsh_history" 2>/dev/null || true' },
137
- { location: 'process listing / argv', command: 'ps -eo args 2>/dev/null || ps aux 2>/dev/null || true' },
138
- { location: 'process env', command: 'env' },
139
- ];
140
- }
141
- /**
142
- * Grep the sandbox filesystem + process table for token values and the
143
- * `x-access-token` marker across every known leak surface (see `tokenLeakProbes`).
144
- * Returns every finding; an empty array means the transient operations left no
145
- * residue. This is the load-bearing check the whole slice exists to make.
146
- *
147
- * NOTE (local-sim): the reflog/config/credential-store/history probes run
148
- * meaningfully in local-sim, but `ps`/argv coverage is only decisive on the live
149
- * Daytona run (local-sim runs the git clone via a child process on the host, not
150
- * inside an isolated PID namespace). The WS-F memo marks that probe live-only.
151
- */
152
- export async function sweepForTokenResidue(session, opts) {
153
- const markers = opts.markers ?? ['x-access-token'];
154
- const tokens = opts.tokens.filter(token => token.length > 0);
155
- const probes = opts.probes ?? tokenLeakProbes(opts.repoPath);
156
- const findings = [];
157
- for (const probe of probes) {
158
- const { stdout } = await session.exec(probe.command);
159
- for (const token of tokens) {
160
- if (stdout.includes(token)) {
161
- findings.push({ location: probe.location, marker: 'token-value' });
162
- }
163
- }
164
- for (const marker of markers) {
165
- if (stdout.includes(marker)) {
166
- findings.push({ location: probe.location, marker });
167
- }
168
- }
169
- }
170
- return findings;
171
- }
172
122
  // -- The workflow ------------------------------------------------------------
173
123
  /**
174
124
  * Run a command in the sandbox and throw on a non-zero (or missing) exit code,
@@ -45,17 +45,20 @@ export function resolveAttachedWorkspaceId(cwd) {
45
45
  const manifestPath = join(dir, 'orizu.team.json');
46
46
  if (existsSync(manifestPath)) {
47
47
  const manifest = readJsonManifest(manifestPath);
48
- const canonical = manifest?.canonical;
49
- const serviceId = canonical && typeof canonical === 'object' && !Array.isArray(canonical)
50
- ? canonical.serviceId
51
- : null;
52
- if (typeof serviceId === 'string' && serviceId)
53
- return serviceId;
48
+ // `setup.attachedWorkspaceId` is the attachment id; `canonical.serviceId`
49
+ // is a legacy duplicate kept only as a read fallback for old repos
50
+ // (ALI-1075: fresh manifests carry no `canonical` block).
54
51
  const setup = manifest?.setup;
55
52
  const attached = setup && typeof setup === 'object' && !Array.isArray(setup)
56
53
  ? setup.attachedWorkspaceId
57
54
  : null;
58
- return typeof attached === 'string' && attached ? attached : null;
55
+ if (typeof attached === 'string' && attached)
56
+ return attached;
57
+ const canonical = manifest?.canonical;
58
+ const serviceId = canonical && typeof canonical === 'object' && !Array.isArray(canonical)
59
+ ? canonical.serviceId
60
+ : null;
61
+ return typeof serviceId === 'string' && serviceId ? serviceId : null;
59
62
  }
60
63
  const parent = dirname(dir);
61
64
  if (parent === dir)
@@ -14,7 +14,7 @@
14
14
  * is immediately rewritten to a clean origin.
15
15
  */
16
16
  import { spawnSync } from 'child_process';
17
- import { mkdirSync } from 'fs';
17
+ import { existsSync, mkdirSync } from 'fs';
18
18
  import { join } from 'path';
19
19
  import { authedFetch } from './http.js';
20
20
  import { assertWorkspaceDirUsable } from './workspace.js';
@@ -196,6 +196,23 @@ export async function runHostedAttach(options, io) {
196
196
  }
197
197
  }
198
198
  const cleanUrl = `https://github.com/${repoFullName}.git`;
199
+ const ensureOrizuDir = io.ensureOrizuDir ?? ((repoDir) => mkdirSync(join(repoDir, '.orizu'), { recursive: true }));
200
+ // Idempotent re-run (ALI-1069): `orizu setup` in an ALREADY-attached workspace
201
+ // reaches here with `targetDir` holding the prior clone (its `.git` plus the
202
+ // committed orizu.team.json — `assertWorkspaceDirUsable` deliberately exempts
203
+ // that dir as the idempotent case). `git clone` refuses ANY non-empty dir, so
204
+ // an unconditional clone would abort the re-run with a misleading "not an empty
205
+ // directory" error. Short-circuit: re-affirm the credential helper config
206
+ // (idempotent) and return without recloning.
207
+ const repoAlreadyCloned = io.repoAlreadyCloned ?? ((dir) => existsSync(join(dir, '.git')));
208
+ if (repoAlreadyCloned(options.targetDir)) {
209
+ io.print(' Workbench repo already attached — skipping clone.');
210
+ gitOrThrow(io.git, ['config', 'credential.helper', CREDENTIAL_HELPER], { cwd: options.targetDir });
211
+ gitOrThrow(io.git, ['config', 'credential.useHttpPath', 'true'], { cwd: options.targetDir });
212
+ ensureOrizuDir(options.targetDir);
213
+ io.print(' Attached. Configured the Orizu credential helper for git.');
214
+ return { repoFullName, provisioned, targetDir: options.targetDir };
215
+ }
199
216
  io.print(` Cloning ${repoFullName}...`);
200
217
  // Clone with the helper inline + the workspace id in the env so the token is
201
218
  // brokered on demand and never materializes in the URL or on disk.
@@ -211,7 +228,6 @@ export async function runHostedAttach(options, io) {
211
228
  // Persist the helper repo-locally for future pull/push (cwd-resolved).
212
229
  gitOrThrow(io.git, ['config', 'credential.helper', CREDENTIAL_HELPER], { cwd: options.targetDir });
213
230
  gitOrThrow(io.git, ['config', 'credential.useHttpPath', 'true'], { cwd: options.targetDir });
214
- const ensureOrizuDir = io.ensureOrizuDir ?? ((repoDir) => mkdirSync(join(repoDir, '.orizu'), { recursive: true }));
215
231
  ensureOrizuDir(options.targetDir);
216
232
  io.print(' Attached. Configured the Orizu credential helper for git.');
217
233
  return { repoFullName, provisioned, targetDir: options.targetDir };
package/dist/help.js CHANGED
@@ -318,8 +318,8 @@ export const COMMAND_DOCS = [
318
318
  },
319
319
  {
320
320
  path: ['prompts', 'push'],
321
- usage: 'orizu prompts push <dir> [--runner-version <id>] [--project <team/project>] [--parent <version-id>] [--json]',
322
- summary: 'Push a local prompt artifact directory as a new version.',
321
+ usage: 'orizu prompts push <dir> [--runner-version <id>] [--project <team/project>] [--parent <version-id>] [--session <session-id>] [--json]',
322
+ summary: 'Push a local prompt artifact directory as a new version (with --session: a commit-first git draft).',
323
323
  group: 'Prompts and judges',
324
324
  },
325
325
  {
@@ -354,8 +354,8 @@ export const COMMAND_DOCS = [
354
354
  },
355
355
  {
356
356
  path: ['judges', 'push'],
357
- usage: 'orizu judges push <dir> [--runner-version <id>] [--project <team/project>] [--parent <version-id>] [--json]',
358
- summary: 'Push a local judge artifact directory as a new version.',
357
+ usage: 'orizu judges push <dir> [--runner-version <id>] [--project <team/project>] [--parent <version-id>] [--session <session-id>] [--json]',
358
+ summary: 'Push a local judge artifact directory as a new version (with --session: a commit-first git draft).',
359
359
  group: 'Prompts and judges',
360
360
  },
361
361
  {
@@ -747,23 +747,25 @@ export const COMMAND_DOCS = [
747
747
  },
748
748
  {
749
749
  path: ['session', 'start'],
750
- usage: 'orizu session start [--project <team/project>] [--json]',
751
- summary: 'Start a durable workspace session for the attached workspace and print the session id for later resume.',
750
+ usage: 'orizu session start [--project <team/project>] [--workspace <dir>] [--json]',
751
+ summary: 'Start a durable workspace session and print the session id. When run inside the workbench clone (or with --workspace), the remote session branch is fetched and checked out locally, and the exact next git commands are printed.',
752
752
  group: 'Sessions',
753
753
  options: [
754
754
  { name: '--project <team/project>', help: 'Optionally scope the session to a project in the attached workspace.' },
755
+ { name: '--workspace <dir>', help: 'Workbench clone directory when not running from inside it.' },
755
756
  { name: '--json', help: 'Emit the machine-readable session payload.' },
756
757
  ],
757
758
  examples: ['orizu session start', 'orizu session start --project highlight/hip --json'],
758
759
  },
759
760
  {
760
761
  path: ['session', 'status'],
761
- usage: 'orizu session status [--session <id> | --status active|ended] [--json]',
762
- summary: 'Inspect one session by id, including run summaries, or list sessions for the attached workspace.',
762
+ usage: 'orizu session status [--session <id> | --status active|ended] [--workspace <dir>] [--json]',
763
+ summary: 'Inspect one session by id (including run summaries and, when run inside the workbench clone, the session branch with local ahead/behind vs origin), or list sessions for the attached workspace.',
763
764
  group: 'Sessions',
764
765
  options: [
765
766
  { name: '--session <id>', help: 'Resume by session id; does not require the original terminal.' },
766
767
  { name: '--status <status>', help: 'Filter attached-workspace sessions when --session is omitted.', choices: ['active', 'ended'] },
768
+ { name: '--workspace <dir>', help: 'Workbench clone directory when not running from inside it.' },
767
769
  { name: '--json', help: 'Emit the machine-readable session or session-list payload.' },
768
770
  ],
769
771
  examples: ['orizu session status --session sess_123 --json', 'orizu session status --status active'],
@@ -781,15 +783,18 @@ export const COMMAND_DOCS = [
781
783
  },
782
784
  {
783
785
  path: ['session', 'finish'],
784
- usage: 'orizu session finish --session <id> [--project <team/project>] [--json]',
785
- summary: 'Finish a hosted session branch: no changes deletes the branch; changes create a repo_merge promotion manifest to review and approve (no GitHub PR).',
786
+ usage: 'orizu session finish --session <id> [--project <team/project>] [--push [--message <text>]] [--workspace <dir>] [--json]',
787
+ summary: 'Finish a hosted session branch: no changes deletes the branch; changes create a repo_merge promotion manifest to review and approve (no GitHub PR). Warns when your local session-branch checkout has uncommitted/unpushed work; --push stages, commits, and pushes it first.',
786
788
  group: 'Sessions',
787
789
  options: [
788
790
  { name: '--session <id>', help: 'Session id whose branch to finish.', required: true },
789
791
  { name: '--project <team/project>', help: 'Project scope for the manifest when the session is not already project-scoped.' },
792
+ { name: '--push', help: 'Opt-in: stage, commit, and push local session-branch work before finishing (raw git stays the default workflow).' },
793
+ { name: '--message <text>', help: 'Commit message for --push (a default is used when omitted).' },
794
+ { name: '--workspace <dir>', help: 'Workbench clone directory when not running from inside it.' },
790
795
  { name: '--json', help: 'Emit the machine-readable outcome (no-changes or the created manifest).' },
791
796
  ],
792
- examples: ['orizu session finish --session sess_123', 'orizu session finish --session sess_123 --project highlight/hip --json'],
797
+ examples: ['orizu session finish --session sess_123', 'orizu session finish --session sess_123 --push --message "tune judge prompt"'],
793
798
  },
794
799
  {
795
800
  path: ['run', 'start'],