@jigging/jig 0.1.0-alpha.12 → 0.1.0-alpha.14

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/README.md CHANGED
@@ -1,201 +1,49 @@
1
1
  # `@jigging/jig`
2
2
 
3
- Jig is a local, secure host for admitted FLOW packages. The direct-run alpha
4
- has one finite command surface:
5
-
6
- ```text
7
- jig init --bare <directory>
8
- jig review [project] [--yes]
9
- jig run <flow:path|binding:id> [--input JSON] [--timeout DURATION]
10
- jig --version
11
- ```
12
-
13
- `jig --version` prints the built package version without opening a project or
14
- acquiring a sandbox.
15
-
16
- There is no `jig setup`. `review` and `run` transparently acquire the rootless
17
- authority they need or fail closed.
3
+ Put reusable Agent methods to work with powers you approve. Choose your Agents,
4
+ combine specialists, inspect their results, and stop owned work.
18
5
 
19
6
  ## Install
20
7
 
21
8
  ```console
22
- npm install --global @jigging/jig@0.1.0-alpha.10
9
+ npm install --global @jigging/jig@alpha
23
10
  ```
24
11
 
25
- Installing Jig also installs `@oven/bun-linux-x64-baseline@1.3.3` as an exact
26
- external runtime dependency. Bun is not embedded in or bundled with the
27
- `@jigging/jig` archive. npm verifies the installed package; Jig selects only
28
- that closed package-local path, authenticates its version, revision, and digest
29
- before evaluator or Flow bytes execute, and revalidates it before each launch.
30
-
31
- ## Supported host
32
-
33
- The alpha has been independently tested on a provisioned Ubuntu 24.04 x86_64
34
- host. Other Linux x86_64 hosts meeting the requirements below have not yet been
35
- independently validated; Jig fails closed when a required capability is absent.
36
-
37
- The alpha requires:
38
-
39
- - Linux x86_64 with glibc 2.17 or newer and an SSE4.2-capable baseline CPU;
40
- - Bubblewrap 0.12 or newer and GNU `readlink -f`, located as described below;
41
- - cgroup v2 with delegated `cpu`, `memory`, and `pids` controllers;
42
- - a systemd user manager with transient delegated scopes; and
43
- - unprivileged user, mount, PID, network, IPC, UTS, and cgroup namespaces.
12
+ The developer alpha requires Linux x86_64/glibc, Bubblewrap, and a systemd user
13
+ manager with delegated cgroup v2 controllers. See the
14
+ [complete supported-host requirements](https://jig.md/guide/#supported-host).
15
+ npm installs Jig's exact Bun runtime dependency alongside the package.
44
16
 
45
- The glibc and SSE4.2 floors come from the selected Bun baseline runtime.
17
+ ## Get started
46
18
 
47
- Host tools are resolved through `/usr/bin`, `/bin`, or the NixOS system profile
48
- `/run/current-system/sw/bin`, never ambient `PATH`. An operator may select
49
- Bubblewrap using an absolute `JIG_BWRAP_PATH`, including a shell-local Nix
50
- dependency. It receives the same validation and never falls back on failure.
51
- On NixOS, enable
52
- `programs.nix-ld.enable` for the unmodified npm runtime. Jig uses the real glibc
53
- loader at the system-managed `share/nix-ld/lib/ld.so` link, mounting only that
54
- loader and its required libraries into Runs. It does not mount nix-ld or the
55
- whole Nix store. NixOS path support is included in this source candidate;
56
- independent NixOS host conformance is not yet established.
19
+ [Create your first Flow](https://jig.md/guide/#your-first-flow), or try
20
+ [an issue becoming a tested patch](https://jig.md/guide/tested-patch): selected
21
+ local source becomes a reviewable patch with executed checks, while the original
22
+ repository stays unchanged.
57
23
 
58
- Jig commands do not use `sudo`, download runtimes, or expose host control to
59
- Flow code. Runtime installation is handled once by npm with the Jig package.
60
-
61
- Flow Runs default to 30 seconds. `jig run --timeout DURATION` accepts a
62
- positive integer followed by `ms`, `s`, `m`, or `h`, up to 24 hours. Flow
63
- execution scopes remain fixed at 256 MiB aggregate memory, 48 aggregate PIDs,
64
- and 50% of one CPU. Project evaluation is fixed at 3 seconds, 256 MiB, 64
65
- PIDs, and 50% CPU. Locked dependency preparation is fixed at 60 seconds, 512
66
- MiB, 64 PIDs, and one CPU. See the repository
67
- [`SECURITY.md`](https://github.com/jiggy/jig/blob/main/SECURITY.md) for the
68
- threat boundary and private reporting channel.
69
-
70
- ## Use
71
-
72
- ```console
73
- jig init --bare my-project
74
- cd my-project
75
- ```
76
-
77
- Place packages under `flows/<name>/`. Each package has exact-case `FLOW.md`
78
- and, for this alpha, one `flow.ts`. The generated `jig.ts`
79
- explicitly discovers `./flows` and `./bindings`.
80
-
81
- The paired `@jigging/flow@0.1.0-alpha.6` package provides the small Run/1
82
- authoring API. Declare it exactly in the Flow's `package.json`, generate a text
83
- `bun.lock` with Bun 1.3.3 and `bun install --lockfile-only`, then handle one
84
- finite Run:
85
-
86
- ```ts
87
- import { handle } from "@jigging/flow";
88
-
89
- await handle(async (run) => ({
90
- outcome: "done",
91
- output: { received: run.input },
92
- }));
93
- ```
24
+ Jig has three project commands:
94
25
 
95
- ```console
96
- jig review
97
- jig run flow:flows/example --input '{}'
98
- ```
99
-
100
- For a longer Run, add a bounded duration such as `--timeout 2m`. The timeout
101
- starts when Jig accepts the root Run. Project acquisition happens before it;
102
- mandatory fencing and cleanup may finish afterward.
103
-
104
- `jig review` shows the complete proposed project change, including exact
105
- current and proposed Package/1 content digests, and asks for approval. It is
106
- not a source-file diff; inspect editable source with your normal tools. When
107
- standard input and output are both terminals, Jig prompts for approval.
108
- Otherwise it prints the review and exits with `JIG_APPROVAL_REQUIRED`; `--yes`
109
- records an already explicit approval. The CLI keeps review and admission
110
- mechanics internal.
111
-
112
- A target is marked changed when its package identity or exact execution
113
- evidence changes. Package digests appear once in the package section;
114
- host-specific evidence remains private.
115
-
116
- For ordinary dependencies, place `package.json` and text `bun.lock` beside
117
- `flow.ts`; do not include `node_modules`. `bun install --lockfile-only` is the
118
- author-side lock-generation step: it creates no project-local `node_modules`,
119
- but may use the network and Bun's author-side cache. On the first `jig review`
120
- after those inputs change, Jig fetches the frozen production artifacts and
121
- materializes a private execution snapshot in a contained trusted preparation
122
- process, using the fixed Bun runtime, the default npm registry, and no
123
- lifecycle scripts. It reuses the Run containment and ownership mechanism but
124
- deliberately has registry network access. A declined review may therefore have
125
- materialized inert evidence without granting authority. Unsupported or
126
- unlocked sources fail before admission.
127
-
128
- Unreleased code can remain ordinary readable files inside the Flow package and
129
- be imported relatively. Code shared from elsewhere must be copied or bundled
130
- into the finished package before `jig review`; Jig does not follow symlinks or
131
- resolve `file:`, `workspace:`, or Git dependencies and does not own that build
132
- step.
133
-
134
- A dependency-free package needs no `bun.lock`; omit it rather than preserving
135
- an empty or stale lock. Optional package schemas are FLOW Schema/1 files and
136
- must begin with the exact root declaration
137
- `"$schema": "https://flow.jig.md/schemas/schema-1.json"`.
138
-
139
- The admitted Run uses only the retained prepared bytes. It has no network,
140
- ambient `PATH`, package installation, or lifecycle scripts. Packages with no
141
- runtime dependencies continue to run directly, and already bundled
142
- package-local code remains valid. An unchanged `jig review` reuses the admitted
143
- prepared tree only while its source and host evidence still match.
144
-
145
- Bindings use an explicit target. A declaration may also give its package exact
146
- child-Flow slots:
147
-
148
- ```ts
149
- import { defineBinding } from "@jigging/jig";
150
-
151
- export default defineBinding({
152
- package: "./flows/reviewer",
153
- slots: { research: "flow:flows/research" },
154
- });
155
- ```
156
-
157
- ```console
158
- jig run binding:reviewer --input '{}'
26
+ ```text
27
+ jig init --bare <directory>
28
+ jig review [project] [--yes]
29
+ jig run <flow:path|binding:id> [options]
159
30
  ```
160
31
 
161
- `slots` is optional and omission normalizes to `{}`. It maps at most 256
162
- LocalName keys to explicit `flow:<path>` or `binding:<id>` targets in the same
163
- admitted generation. Slots belong to the Binding; a direct `flow:` Run has none.
164
- Selected child Bindings must have no child slots of their own.
165
-
166
- Each `flow/call` carries JSON/1 input into a fresh child context and returns a
167
- complete JSON/1 Run result. The child receives its selected target's admitted
168
- settings and Agent capability, empty attachments, no inherited slots, and a
169
- deadline no later than its parent. It selects its own package-local Skills per
170
- Agent call; providers and credentials remain host choices. Run/1 controls
171
- operation identity, duplicate joins, conflicting reuse, cancellation, and
172
- uncertainty; Jig does not replay uncertain dispatch automatically. There is no
173
- separate child history, administration, scheduler, catalogue, or resolver. Jig
174
- also never guesses an unprefixed target.
175
-
176
- Each Run context admits one active operation; a child Flow can own one Agent
177
- operation while its parent awaits it. Excess distinct concurrent calls receive
178
- `RESOURCE_EXHAUSTED`, while sequential calls remain available. Cancellation and
179
- cleanup cover the specialist and its locally owned Agent worker.
32
+ `review` asks you to approve the proposed project changes. `run` executes the
33
+ admitted revision and returns JSON after settling owned work. Use
34
+ `jig --version` to inspect the installed version.
180
35
 
181
- One experimental [Agent Run capability](https://jig.md/spec/agent-run) is
182
- available through ordinary Run/1 `effect/call`. The host may use the official
183
- OpenAI JavaScript SDK against an operator-selected OpenAI-compatible endpoint,
184
- or run native Codex, Claude Code, or Pi through one private ACP mechanism.
185
- Direct API configuration uses `OPENAI_API_KEY` and `OPENAI_MODEL`; optional
186
- `OPENAI_BASE_URL` and `OPENAI_API` select the HTTPS endpoint and either the
187
- `responses` (default) or `chat-completions` wire shape. Jig supplies no default
188
- model. Client, API, endpoint, model, executable path, and credentials are
189
- trusted host configuration, not FLOW or Binding inputs, and Jig exposes no
190
- provider registry.
36
+ ## Guides
191
37
 
192
- The complete runnable example and current specification map are in the
193
- [repository README](https://github.com/jiggy/jig#readme) and
194
- [`docs/jig/guide/index.md`](https://github.com/jiggy/jig/blob/main/docs/jig/guide/index.md).
38
+ - [Choose an Agent](https://jig.md/guide/agents)
39
+ - [Working with files](https://jig.md/guide/files)
40
+ - [Dependencies](https://jig.md/guide/dependencies)
41
+ - [Workflow design](https://jig.md/guide/workflow-design)
42
+ - [Project authoring](https://jig.md/spec/project-sdk)
43
+ - [Execution policy](https://jig.md/spec/project-policy)
44
+ - [Security boundary](https://github.com/jiggy/jig/blob/main/SECURITY.md)
195
45
 
196
- This is a prerelease alpha. It does not expose Services, Hooks, event sources,
197
- a provider registry or package-selected provider configuration, Agent sessions,
198
- Semantic Choice, Jig Graph, schedulers, catalogues, runtime registries, or
199
- sandbox registries.
46
+ Jig is prerelease software. [FLOW](https://flow.jig.md/) remains an independent
47
+ standard for portable methods.
200
48
 
201
49
  Copyright © 2026 Victor Duarte <zvictor> and contributors.
package/dist/index.js CHANGED
@@ -105,6 +105,43 @@ function validateString(value, byteLimit) {
105
105
  }
106
106
  }
107
107
 
108
+ // src/project/commands.ts
109
+ function projectCommandPath(value) {
110
+ if (typeof value !== "string" || value.length > 256 || !/^[A-Za-z0-9_][A-Za-z0-9_./-]*$/.test(value) || value.split("/").some((part) => part === "" || part === "." || part === "..") || value.split("/").length > 16)
111
+ throw new TypeError("command file paths must be bounded relative paths without traversal");
112
+ return value;
113
+ }
114
+ function normalizeProjectCommands(value) {
115
+ if (value === null || typeof value !== "object" || Array.isArray(value))
116
+ throw new TypeError("commands must be an object");
117
+ const entries = Object.entries(value);
118
+ if (entries.length > 8)
119
+ throw new TypeError("commands exceed eight entries");
120
+ const output = Object.create(null);
121
+ for (const [name, item] of entries.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
122
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name) || name.length > 64)
123
+ throw new TypeError("command name must be a LocalName");
124
+ if (item === null || typeof item !== "object" || Array.isArray(item) || Object.keys(item).length !== 1)
125
+ throw new TypeError(`command ${name} must contain run or test`);
126
+ const command = item;
127
+ if (Object.hasOwn(command, "run")) {
128
+ const path = projectCommandPath(command.run);
129
+ if (!path.endsWith(".ts") && !path.endsWith(".js"))
130
+ throw new TypeError("command run must name a .ts or .js entrypoint");
131
+ output[name] = Object.freeze({ run: path });
132
+ } else if (Object.hasOwn(command, "test") && Array.isArray(command.test)) {
133
+ if (command.test.length === 0 || command.test.length > 16)
134
+ throw new TypeError("command test must select one to sixteen test files");
135
+ const paths = command.test.map(projectCommandPath);
136
+ if (new Set(paths).size !== paths.length || paths.some((path) => !/\.(test|spec)\.(ts|js)$/.test(path)))
137
+ throw new TypeError("command test must name distinct .test or .spec files");
138
+ output[name] = Object.freeze({ test: Object.freeze(paths) });
139
+ } else
140
+ throw new TypeError(`command ${name} must contain run or test`);
141
+ }
142
+ return Object.freeze(output);
143
+ }
144
+
108
145
  // src/package/case-fold-15.1.ts
109
146
  var CASE_FOLD_15_1 = new Map([
110
147
  [65, "a"],
@@ -1797,7 +1834,7 @@ function bindingRef(id) {
1797
1834
  }
1798
1835
  function normalizeBinding(input, canonical) {
1799
1836
  const captured = snapshotJsonObject(input, "Binding definition");
1800
- assertClosedObject(captured, canonical ? ["kind", "package", "settings", "slots"] : ["package", "settings", "slots"], "Binding definition");
1837
+ assertClosedObject(captured, canonical ? ["kind", "package", "settings", "slots", "commands"] : ["package", "settings", "slots", "commands"], "Binding definition");
1801
1838
  if (canonical && captured.kind !== "package") {
1802
1839
  throw new TypeError("Binding kind must be package");
1803
1840
  }
@@ -1806,11 +1843,13 @@ function normalizeBinding(input, canonical) {
1806
1843
  const packagePath = normalizeProjectPath(captured.package, "package");
1807
1844
  const settings = Object.hasOwn(captured, "settings") ? expectJsonObject(captured.settings, "settings") : emptyRecord();
1808
1845
  const slots = Object.hasOwn(captured, "slots") ? normalizeFlowSlots(captured.slots) : emptyRecord();
1846
+ const commands = normalizeProjectCommands(Object.hasOwn(captured, "commands") ? captured.commands : {});
1809
1847
  return record({
1810
1848
  kind: "package",
1811
1849
  package: packagePath,
1812
1850
  settings,
1813
- slots
1851
+ slots,
1852
+ ...Object.keys(commands).length === 0 ? {} : { commands }
1814
1853
  });
1815
1854
  }
1816
1855
  function normalizeFlowSlots(value) {
@@ -1,4 +1,5 @@
1
1
  import type { JsonObject } from '../json.js';
2
+ import { type ProjectCommands } from './commands.js';
2
3
  export interface DiscoverySource {
3
4
  readonly kind: 'discover';
4
5
  readonly roots: readonly string[];
@@ -31,11 +32,13 @@ export interface PackageBindingDefinition {
31
32
  readonly package: string;
32
33
  readonly settings: JsonObject;
33
34
  readonly slots: Readonly<Record<string, string>>;
35
+ readonly commands?: ProjectCommands;
34
36
  }
35
37
  export interface PackageBindingInput {
36
38
  readonly package: string;
37
39
  readonly settings?: JsonObject;
38
40
  readonly slots?: Readonly<Record<string, string>>;
41
+ readonly commands?: ProjectCommands;
39
42
  }
40
43
  export type BindingDefinition = PackageBindingDefinition;
41
44
  export declare function discover(roots: string | readonly string[]): DiscoverySource;
@@ -0,0 +1,10 @@
1
+ /** Reviewed invocation policy; runtime and containment remain host choices. */
2
+ export type ProjectCommand = {
3
+ readonly run: string;
4
+ } | {
5
+ readonly test: readonly string[];
6
+ };
7
+ export type ProjectCommands = Readonly<Record<string, ProjectCommand>>;
8
+ export declare function projectCommandPath(value: unknown): string;
9
+ /** Input must already be an ordinary JSON snapshot at the owning boundary. */
10
+ export declare function normalizeProjectCommands(value: unknown): ProjectCommands;
@@ -102,6 +102,9 @@
102
102
  "$ref": "#/$defs/targetSelector"
103
103
  },
104
104
  "maxProperties": 256
105
+ },
106
+ "commands": {
107
+ "$ref": "#/$defs/commands"
105
108
  }
106
109
  },
107
110
  "required": ["kind", "package", "settings", "slots"],
@@ -109,6 +112,43 @@
109
112
  },
110
113
  "bindingDefinition": {
111
114
  "$ref": "#/$defs/packageBinding"
115
+ },
116
+ "commands": {
117
+ "type": "object",
118
+ "maxProperties": 8,
119
+ "additionalProperties": {
120
+ "oneOf": [
121
+ {
122
+ "type": "object",
123
+ "properties": {
124
+ "run": {
125
+ "type": "string",
126
+ "minLength": 1,
127
+ "maxLength": 256
128
+ }
129
+ },
130
+ "required": ["run"],
131
+ "additionalProperties": false
132
+ },
133
+ {
134
+ "type": "object",
135
+ "properties": {
136
+ "test": {
137
+ "type": "array",
138
+ "minItems": 1,
139
+ "maxItems": 16,
140
+ "items": {
141
+ "type": "string",
142
+ "minLength": 1,
143
+ "maxLength": 256
144
+ }
145
+ }
146
+ },
147
+ "required": ["test"],
148
+ "additionalProperties": false
149
+ }
150
+ ]
151
+ }
112
152
  }
113
153
  }
114
154
  }
@@ -105,6 +105,43 @@ function validateString(value, byteLimit) {
105
105
  }
106
106
  }
107
107
 
108
+ // src/project/commands.ts
109
+ function projectCommandPath(value) {
110
+ if (typeof value !== "string" || value.length > 256 || !/^[A-Za-z0-9_][A-Za-z0-9_./-]*$/.test(value) || value.split("/").some((part) => part === "" || part === "." || part === "..") || value.split("/").length > 16)
111
+ throw new TypeError("command file paths must be bounded relative paths without traversal");
112
+ return value;
113
+ }
114
+ function normalizeProjectCommands(value) {
115
+ if (value === null || typeof value !== "object" || Array.isArray(value))
116
+ throw new TypeError("commands must be an object");
117
+ const entries = Object.entries(value);
118
+ if (entries.length > 8)
119
+ throw new TypeError("commands exceed eight entries");
120
+ const output = Object.create(null);
121
+ for (const [name, item] of entries.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
122
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name) || name.length > 64)
123
+ throw new TypeError("command name must be a LocalName");
124
+ if (item === null || typeof item !== "object" || Array.isArray(item) || Object.keys(item).length !== 1)
125
+ throw new TypeError(`command ${name} must contain run or test`);
126
+ const command = item;
127
+ if (Object.hasOwn(command, "run")) {
128
+ const path = projectCommandPath(command.run);
129
+ if (!path.endsWith(".ts") && !path.endsWith(".js"))
130
+ throw new TypeError("command run must name a .ts or .js entrypoint");
131
+ output[name] = Object.freeze({ run: path });
132
+ } else if (Object.hasOwn(command, "test") && Array.isArray(command.test)) {
133
+ if (command.test.length === 0 || command.test.length > 16)
134
+ throw new TypeError("command test must select one to sixteen test files");
135
+ const paths = command.test.map(projectCommandPath);
136
+ if (new Set(paths).size !== paths.length || paths.some((path) => !/\.(test|spec)\.(ts|js)$/.test(path)))
137
+ throw new TypeError("command test must name distinct .test or .spec files");
138
+ output[name] = Object.freeze({ test: Object.freeze(paths) });
139
+ } else
140
+ throw new TypeError(`command ${name} must contain run or test`);
141
+ }
142
+ return Object.freeze(output);
143
+ }
144
+
108
145
  // src/package/case-fold-15.1.ts
109
146
  var CASE_FOLD_15_1 = new Map([
110
147
  [65, "a"],
@@ -1797,7 +1834,7 @@ function bindingRef(id) {
1797
1834
  }
1798
1835
  function normalizeBinding(input, canonical) {
1799
1836
  const captured = snapshotJsonObject(input, "Binding definition");
1800
- assertClosedObject(captured, canonical ? ["kind", "package", "settings", "slots"] : ["package", "settings", "slots"], "Binding definition");
1837
+ assertClosedObject(captured, canonical ? ["kind", "package", "settings", "slots", "commands"] : ["package", "settings", "slots", "commands"], "Binding definition");
1801
1838
  if (canonical && captured.kind !== "package") {
1802
1839
  throw new TypeError("Binding kind must be package");
1803
1840
  }
@@ -1806,11 +1843,13 @@ function normalizeBinding(input, canonical) {
1806
1843
  const packagePath = normalizeProjectPath(captured.package, "package");
1807
1844
  const settings = Object.hasOwn(captured, "settings") ? expectJsonObject(captured.settings, "settings") : emptyRecord();
1808
1845
  const slots = Object.hasOwn(captured, "slots") ? normalizeFlowSlots(captured.slots) : emptyRecord();
1846
+ const commands = normalizeProjectCommands(Object.hasOwn(captured, "commands") ? captured.commands : {});
1809
1847
  return record({
1810
1848
  kind: "package",
1811
1849
  package: packagePath,
1812
1850
  settings,
1813
- slots
1851
+ slots,
1852
+ ...Object.keys(commands).length === 0 ? {} : { commands }
1814
1853
  });
1815
1854
  }
1816
1855
  function normalizeFlowSlots(value) {