@reunionstudio/airlock-mcp 0.1.2 → 0.1.3
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/.agents/skills/airlock-mcp/SKILL.md +95 -5
- package/.agents/skills/airlock-mcp/agents/openai.yaml +2 -2
- package/README.md +52 -10
- package/docs/architecture.md +17 -6
- package/docs/install-surface.md +32 -2
- package/docs/spec-workbench-architecture.md +14 -3
- package/docs/workflows.md +87 -3
- package/package.json +1 -1
- package/setup.py +1 -1
- package/src/airlock_mcp/__init__.py +1 -1
- package/src/airlock_mcp/app_context.py +271 -0
- package/src/airlock_mcp/art.py +1 -1
- package/src/airlock_mcp/bootstrap.py +49 -4
- package/src/airlock_mcp/cli.py +32 -0
- package/src/mcp.mjs +5 -5
- package/src/text.mjs +50 -16
- package/src/workbench.mjs +63 -0
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: airlock-mcp
|
|
3
|
-
description: Design, draft, review, and iterate Airlock specs in the Airlock MCP spec-building workbench. Use when Codex is helping a person choose a small starting spec, work through row grain and OODA-loop decisions, import reusable patterns such as posts or guest access setups, validate local draft files,
|
|
3
|
+
description: Design, draft, review, and iterate Airlock specs in the Airlock MCP spec-building workbench, or help build apps and workflows that use existing Airlock specs safely. Use when Codex is helping a person choose a small starting spec, work through row grain and OODA-loop decisions, import reusable patterns such as posts or guest access setups, validate local draft files, prepare an Airlock spec config before installed Airlock validation, or code an app that reads from and submits through existing specs.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Airlock MCP
|
|
6
|
+
# Airlock MCP
|
|
7
7
|
|
|
8
8
|
Use this skill to help a human develop Airlock specs over multiple sessions.
|
|
9
|
-
|
|
10
|
-
the
|
|
9
|
+
Also use it when a human wants to build an app or workflow that uses existing
|
|
10
|
+
Airlock specs. The specs repo is the durable workbench for spec drafts; an app
|
|
11
|
+
repo is the right place for code that reads, orients, decides, and submits
|
|
12
|
+
through existing specs. Codex is the conversational interface; the CLI is the
|
|
13
|
+
deterministic checker for local spec drafts.
|
|
11
14
|
|
|
12
15
|
## Default Path
|
|
13
16
|
|
|
@@ -16,6 +19,13 @@ the deterministic checker.
|
|
|
16
19
|
`sample.records.json`, and `review.md`.
|
|
17
20
|
2. If the current repo is not yet an Airlock MCP specs repo, run or suggest:
|
|
18
21
|
`airlock-mcp init-repo`.
|
|
22
|
+
Exception: if the user asks to build an app, dashboard, approval queue,
|
|
23
|
+
decision UI, agent workflow, or other code that uses existing specs, do not
|
|
24
|
+
bootstrap a specs repo just because the current repo lacks `workspaces/`.
|
|
25
|
+
Treat the current repo as an app repo unless the user asks to create specs.
|
|
26
|
+
Ask for the development mode, available specs, app goal, and read/write
|
|
27
|
+
contract instead. Use `airlock-mcp init-app-context` when the app repo needs
|
|
28
|
+
an `airlock/` folder with spec snapshots and an app manifest.
|
|
19
29
|
When helping create that repo from scratch, ask for the project or
|
|
20
30
|
organization name and suggest `<slug>-specs`; for example, `Home` becomes
|
|
21
31
|
`home-specs`. Reserve `airlock-specs` for the canonical reusable spec
|
|
@@ -35,7 +45,9 @@ the deterministic checker.
|
|
|
35
45
|
any workspace.
|
|
36
46
|
If the user opened the public `airlock-mcp` workbench repo as the entry
|
|
37
47
|
point, help them create a separate `<slug>-specs` project repo beside it.
|
|
38
|
-
3. If no workspace exists, welcome the user and ask:
|
|
48
|
+
3. If no workspace exists, welcome the user and ask which mode they want:
|
|
49
|
+
spec-first, app-first from existing specs, or co-development.
|
|
50
|
+
For spec-first or co-development, ask:
|
|
39
51
|
`What process do you want to improve?`
|
|
40
52
|
Explain that Airlock works best when we can identify the loop around that
|
|
41
53
|
process:
|
|
@@ -159,6 +171,84 @@ Use the observe-orient-decide-act loop as the product frame:
|
|
|
159
171
|
- Act: controlled writes back to interfaces, commitments, follow-ups, outputs,
|
|
160
172
|
and the next observations created by those actions.
|
|
161
173
|
|
|
174
|
+
## Apps And Workflows Using Existing Specs
|
|
175
|
+
|
|
176
|
+
Use this path when the user wants to vibe-code an app, dashboard, queue,
|
|
177
|
+
decision UI, analysis workflow, or agent workflow that uses specs they already
|
|
178
|
+
have access to. This is not spec editing by default.
|
|
179
|
+
|
|
180
|
+
Use three delivery modes:
|
|
181
|
+
|
|
182
|
+
- Spec-first: design governed specs, samples, access, and validation before
|
|
183
|
+
building the app surface.
|
|
184
|
+
- App-first: use existing specs to build an app, dashboard, queue, or workflow.
|
|
185
|
+
- Co-development: develop the app and specs together, keeping the contract and
|
|
186
|
+
experience visible side by side.
|
|
187
|
+
|
|
188
|
+
Start by identifying:
|
|
189
|
+
|
|
190
|
+
1. The app goal: what the user needs to orient around or decide.
|
|
191
|
+
2. Read specs: existing specs the app reads from, such as budgets, expenses,
|
|
192
|
+
requests, forecasts, observations, payouts, or reference data.
|
|
193
|
+
3. Write specs: existing specs the app submits to, such as decisions,
|
|
194
|
+
approvals, comments, commitments, actions, or follow-ups.
|
|
195
|
+
4. The app surface: Streamlit, web app, CLI, notebook, scheduled agent, or
|
|
196
|
+
other runtime.
|
|
197
|
+
5. The Airlock access path: installed Airlock procedures, approved MCP tools,
|
|
198
|
+
generated read surfaces, stages, or documented SQL helpers available in the
|
|
199
|
+
user's environment.
|
|
200
|
+
6. Identity, evidence, timestamps, approval, and separation-of-duties rules.
|
|
201
|
+
|
|
202
|
+
Help the app follow the loop:
|
|
203
|
+
|
|
204
|
+
- Observe/read: fetch existing governed data through approved Airlock or
|
|
205
|
+
Snowflake surfaces.
|
|
206
|
+
- Orient: summarize, compare, rank, flag exceptions, propose options, or build
|
|
207
|
+
dashboards and queues.
|
|
208
|
+
- Decide: capture human or agent choices with rationale and evidence.
|
|
209
|
+
- Act/write: submit the decision, approval, action, or follow-up through an
|
|
210
|
+
Airlock spec contract.
|
|
211
|
+
|
|
212
|
+
Keep the boundary clear:
|
|
213
|
+
|
|
214
|
+
- Do not edit specs unless the user asks for spec changes.
|
|
215
|
+
- Do not write directly to Airlock-owned tables, stages, generated views, or
|
|
216
|
+
generated tables.
|
|
217
|
+
- Do not treat Snowflake as an unrestricted database just because the app can
|
|
218
|
+
connect to it.
|
|
219
|
+
- Prefer existing Airlock contracts for reads and writes. If no suitable write
|
|
220
|
+
spec exists, explain the gap and propose a small decision/action spec as a
|
|
221
|
+
separate spec-design step.
|
|
222
|
+
- Keep secrets and credentials out of repo files. Use the app's existing secret
|
|
223
|
+
management pattern.
|
|
224
|
+
|
|
225
|
+
### App Context Seeding
|
|
226
|
+
|
|
227
|
+
Use `airlock-mcp init-app-context` in an app repo when the app needs local
|
|
228
|
+
Airlock context. The command creates:
|
|
229
|
+
|
|
230
|
+
```text
|
|
231
|
+
airlock/
|
|
232
|
+
AGENTS.md
|
|
233
|
+
README.md
|
|
234
|
+
specs.manifest.json
|
|
235
|
+
spec-snapshots/
|
|
236
|
+
sample-records/
|
|
237
|
+
generated/
|
|
238
|
+
types/
|
|
239
|
+
sql/
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The manifest and snapshots help the app develop against stable local references.
|
|
243
|
+
They are not canonical. Canonical specs live in the specs repo or installed
|
|
244
|
+
Airlock. When seeding from draft workspaces, pass `--spec <workspace>` for each
|
|
245
|
+
spec to copy `spec.config.json` and `sample.records.json` into the app context.
|
|
246
|
+
|
|
247
|
+
In co-development mode, keep two tracks visible:
|
|
248
|
+
|
|
249
|
+
- Spec track: row grain, columns, samples, access, validation, workflow.
|
|
250
|
+
- App track: screens, reads, decisions, writes, user actions, runtime.
|
|
251
|
+
|
|
162
252
|
## Pattern Guidance
|
|
163
253
|
|
|
164
254
|
Read only the relevant pattern files:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "Airlock MCP"
|
|
3
|
-
short_description: "
|
|
4
|
-
default_prompt: "Use $airlock-mcp to ask
|
|
3
|
+
short_description: "Design specs or build apps that use Airlock specs."
|
|
4
|
+
default_prompt: "Use $airlock-mcp to ask whether I want to design specs, improve an OODA loop, or build an app/workflow that uses existing specs; then gather artifacts or available specs and help me move safely."
|
package/README.md
CHANGED
|
@@ -8,18 +8,22 @@ It covers the full Airlock loop:
|
|
|
8
8
|
- design specs with the bundled spec-building workbench
|
|
9
9
|
- map the process a person wants to improve into observe, orient, decide, and act
|
|
10
10
|
- use specs for governed data movement, decisions, actions, and feedback loops
|
|
11
|
+
- build apps and workflows that read from and submit through existing specs
|
|
11
12
|
- validate, create, and revise specs against installed Airlock
|
|
12
13
|
|
|
13
|
-
Spec building
|
|
14
|
-
Airlock MCP.
|
|
14
|
+
Spec building and spec-using app guidance are not second things users install.
|
|
15
|
+
They are bundled inside Airlock MCP.
|
|
15
16
|
|
|
16
|
-
Airlock MCP gives agents
|
|
17
|
+
Airlock MCP gives agents three kinds of Airlock help:
|
|
17
18
|
|
|
18
19
|
1. Spec design: draft, check, revise, import, clone, and prepare specs for
|
|
19
20
|
installed Airlock validation.
|
|
20
21
|
2. Airlock operating patterns: use specs to organize observations, orientation,
|
|
21
22
|
governed decisions, controlled actions, separation of duties, and feedback
|
|
22
23
|
loops.
|
|
24
|
+
3. App and workflow implementation: build dashboards, queues, decision UIs,
|
|
25
|
+
analyses, and agent workflows that use existing specs through Airlock
|
|
26
|
+
contracts.
|
|
23
27
|
|
|
24
28
|
## Install
|
|
25
29
|
|
|
@@ -50,7 +54,8 @@ codex mcp add airlock -- npx -y github:reunionstudio/airlock-mcp server
|
|
|
50
54
|
|
|
51
55
|
The server exposes bootstrap guidance for starting a specs repo, coaching a
|
|
52
56
|
person through process discovery, and entering the bundled spec-building
|
|
53
|
-
workbench when a first spec is ready to draft.
|
|
57
|
+
workbench when a first spec is ready to draft. It also guides agents building
|
|
58
|
+
apps or workflows that use specs the user already has access to.
|
|
54
59
|
|
|
55
60
|
Workspace summaries are structured spec cards. They present the current spec
|
|
56
61
|
core, file rules, attachment policy, guest access, column rules, sample record
|
|
@@ -90,6 +95,8 @@ The server exposes orientation plus local spec-building tools:
|
|
|
90
95
|
- `airlock_start`: return setup guidance for a project.
|
|
91
96
|
- `airlock_doctor`: verify bundled workbench assets.
|
|
92
97
|
- `airlock_init_repo`: bootstrap a Git-backed specs repo.
|
|
98
|
+
- `airlock_init_app_context`: seed an app repo with spec snapshots, sample
|
|
99
|
+
records, generated helper folders, and an app manifest.
|
|
93
100
|
- `airlock_list_patterns` and `airlock_show_pattern`: inspect starter patterns.
|
|
94
101
|
- `airlock_init_workspace`: create a workspace from `blank` or `posts`.
|
|
95
102
|
- `airlock_list_workspaces`: inspect active or archived drafts.
|
|
@@ -127,13 +134,48 @@ The intended user flow is:
|
|
|
127
134
|
4. If Codex is creating the repo, choose where the `home-specs` directory should
|
|
128
135
|
live before files are written.
|
|
129
136
|
5. Ask Codex to use Airlock MCP to help improve a process with Airlock specs.
|
|
130
|
-
6.
|
|
131
|
-
|
|
137
|
+
6. Choose a delivery mode: spec-first, app-first from existing specs, or
|
|
138
|
+
co-development of specs and app together.
|
|
139
|
+
7. Let Airlock MCP bootstrap the project, ask what process the user wants to
|
|
140
|
+
improve when specs are involved, and propose a small first spec plus a plan
|
|
141
|
+
for more.
|
|
142
|
+
|
|
143
|
+
The first workspace should not be created automatically. Airlock MCP should
|
|
144
|
+
first ask whether the user wants spec-first, app-first from existing specs, or
|
|
145
|
+
co-development of specs and app together. For spec-building work, it should ask
|
|
146
|
+
for the messy process, identify where information comes in and actions go out,
|
|
147
|
+
then choose a small observation, orient, decision, or action spec.
|
|
148
|
+
|
|
149
|
+
For app-building work, Airlock MCP should identify the app goal, read specs,
|
|
150
|
+
write specs, orienting views, decision capture, and approved Airlock/Snowflake
|
|
151
|
+
access paths. The app should submit decisions, approvals, actions, comments, or
|
|
152
|
+
follow-ups through Airlock spec contracts. It should not write directly to
|
|
153
|
+
Airlock-owned tables or bypass spec workflow.
|
|
154
|
+
|
|
155
|
+
When an app repo needs local Airlock context, use:
|
|
132
156
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
157
|
+
```bash
|
|
158
|
+
airlock-mcp init-app-context . --mode app-first --spec ../home-specs/workspaces/expenses
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
That creates:
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
airlock/
|
|
165
|
+
AGENTS.md
|
|
166
|
+
README.md
|
|
167
|
+
specs.manifest.json
|
|
168
|
+
spec-snapshots/
|
|
169
|
+
sample-records/
|
|
170
|
+
generated/
|
|
171
|
+
types/
|
|
172
|
+
sql/
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The snapshots are for coding, tests, and UI planning. They are not canonical.
|
|
176
|
+
Canonical specs live in the specs repo or installed Airlock. In
|
|
177
|
+
co-development, keep a visible spec track and app track so changes to row
|
|
178
|
+
grain, access, workflow, screens, reads, and governed writes stay aligned.
|
|
137
179
|
|
|
138
180
|
When the user already has artifacts, Airlock MCP should ask for them early:
|
|
139
181
|
CSV or Excel files, JSON samples, API docs, schemas, forms, screenshots, PDFs,
|
package/docs/architecture.md
CHANGED
|
@@ -30,8 +30,9 @@ The spec-building workbench lives in this repo under `src/airlock_mcp`,
|
|
|
30
30
|
`patterns`, `workspaces`, `schemas`, and `.agents/skills/airlock-mcp`.
|
|
31
31
|
Airlock operating patterns live in the same MCP experience: real use cases,
|
|
32
32
|
controlled interface ingestion, OODA loops, governed decisions, separation of
|
|
33
|
-
duties, controlled actions, output review,
|
|
34
|
-
a second install or separate
|
|
33
|
+
duties, controlled actions, output review, improvement capture, and app/workflow
|
|
34
|
+
implementation against existing specs. This is not a second install or separate
|
|
35
|
+
product surface.
|
|
35
36
|
|
|
36
37
|
## MCP Surface
|
|
37
38
|
|
|
@@ -41,14 +42,24 @@ The bootstrap server exposes:
|
|
|
41
42
|
- resource: `airlock://getting-started`
|
|
42
43
|
- orientation tool: `airlock_start`
|
|
43
44
|
- workbench tools: `airlock_doctor`, `airlock_init_repo`,
|
|
44
|
-
`
|
|
45
|
-
`
|
|
46
|
-
`
|
|
45
|
+
`airlock_init_app_context`, `airlock_list_patterns`,
|
|
46
|
+
`airlock_show_pattern`, `airlock_init_workspace`, `airlock_list_workspaces`,
|
|
47
|
+
`airlock_check_workspace`, `airlock_summary`, `airlock_next`,
|
|
48
|
+
`airlock_export_csv`, and `airlock_render_sql`
|
|
47
49
|
|
|
48
50
|
These give the agent enough context to start a Git-backed `<project>-specs`
|
|
49
51
|
repo in a user-chosen location, ask what process the user wants to improve,
|
|
50
52
|
enter the spec-building workbench when a first spec is ready to draft, and
|
|
51
|
-
avoid creating a first workspace until the user chooses a path.
|
|
53
|
+
avoid creating a first workspace until the user chooses a path. They also give
|
|
54
|
+
the agent enough product guidance to work in an app repo when the user wants to
|
|
55
|
+
build software that reads from existing specs and submits governed decisions or
|
|
56
|
+
actions through Airlock contracts.
|
|
57
|
+
|
|
58
|
+
`airlock_init_app_context` is the bridge for app-first and co-development work.
|
|
59
|
+
It creates an app-local `airlock/` folder with spec snapshots, sample records,
|
|
60
|
+
generated helper placeholders, and `specs.manifest.json`. Those files support
|
|
61
|
+
coding, tests, and UI planning, but they are not canonical. Canonical specs
|
|
62
|
+
remain in the specs repo or installed Airlock.
|
|
52
63
|
|
|
53
64
|
Workbench tools call the bundled Python package with argument arrays and a
|
|
54
65
|
controlled `PYTHONPATH`. They do not expose update commands over MCP. Tool
|
package/docs/install-surface.md
CHANGED
|
@@ -16,8 +16,10 @@ Then the user opens Codex, creates or opens a Git-backed `<project>-specs`
|
|
|
16
16
|
repo, and starts chatting with Airlock. GitHub is the recommended default when
|
|
17
17
|
available. If Codex is creating the repo, it should ask where the directory
|
|
18
18
|
should live before writing files. Airlock MCP should start by asking what
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
delivery mode the user wants: spec-first, app-first from existing specs, or
|
|
20
|
+
co-development of specs and app together. Then it should help design specs and
|
|
21
|
+
operating patterns around observe, orient, decide, and act, or help code an app
|
|
22
|
+
that reads and submits through Airlock contracts.
|
|
21
23
|
|
|
22
24
|
The install command is not the spec workspace. It is the connector/setup entry
|
|
23
25
|
point. The specs repo is still the durable memory.
|
|
@@ -119,3 +121,31 @@ The first release should optimize the user journey:
|
|
|
119
121
|
7. map where information comes in and actions go out
|
|
120
122
|
8. create the first workspace only after the user chooses a small first spec
|
|
121
123
|
9. keep a plan for later specs that improve the full loop
|
|
124
|
+
|
|
125
|
+
For users who already have specs and want to build software, optimize a second
|
|
126
|
+
journey:
|
|
127
|
+
|
|
128
|
+
1. install connector
|
|
129
|
+
2. open the app repo in Codex
|
|
130
|
+
3. ask for the app goal and available specs
|
|
131
|
+
4. identify read specs and write specs
|
|
132
|
+
5. seed app-local Airlock context with `airlock-mcp init-app-context` when the
|
|
133
|
+
app needs spec snapshots, samples, generated helpers, and a manifest
|
|
134
|
+
6. build orienting views, dashboards, queues, proposals, or recommendations
|
|
135
|
+
7. capture decisions, approvals, actions, comments, or follow-ups through
|
|
136
|
+
Airlock spec contracts
|
|
137
|
+
8. avoid direct writes to Airlock-owned tables and avoid bypassing spec workflow
|
|
138
|
+
|
|
139
|
+
For users who are developing the app and specs together, optimize a third
|
|
140
|
+
journey:
|
|
141
|
+
|
|
142
|
+
1. open the app repo or specs repo in Codex
|
|
143
|
+
2. choose co-development explicitly
|
|
144
|
+
3. keep a spec track for row grain, columns, access, samples, validation, and
|
|
145
|
+
workflow
|
|
146
|
+
4. keep an app track for screens, reads, orienting summaries, decision capture,
|
|
147
|
+
governed writes, runtime, and tests
|
|
148
|
+
5. seed the app repo with `airlock/` snapshots and a manifest when the app needs
|
|
149
|
+
local contract context
|
|
150
|
+
6. treat app snapshots as development references while canonical specs remain
|
|
151
|
+
in the specs repo or installed Airlock
|
|
@@ -14,6 +14,8 @@ Airlock MCP is intentionally small:
|
|
|
14
14
|
- `art.py`: small terminal identity and about text.
|
|
15
15
|
- `bootstrap.py`: specs-repo bootstrap files, including `AGENTS.md` and the
|
|
16
16
|
repo-scoped Codex skill.
|
|
17
|
+
- `app_context.py`: app-repo Airlock context seeding for spec snapshots,
|
|
18
|
+
sample records, generated helper placeholders, and `specs.manifest.json`.
|
|
17
19
|
- `manage.py`: workspace discovery, archive, restore, rename, and next-action
|
|
18
20
|
helpers.
|
|
19
21
|
- `project.py`: checkout discovery and environment override handling.
|
|
@@ -55,6 +57,8 @@ The old Airlock Streamlit editor used mode-specific hydration:
|
|
|
55
57
|
Airlock MCP maps that to files:
|
|
56
58
|
|
|
57
59
|
- `init-repo`: prepare a separate specs repo for Codex and Airlock MCP
|
|
60
|
+
- `init-app-context`: prepare an app repo `airlock/` folder for app-first or
|
|
61
|
+
co-development work against existing specs
|
|
58
62
|
- `init`: create from a pattern
|
|
59
63
|
- `import-spec`: edit/import an existing canonical or spec-library config in a
|
|
60
64
|
new workspace
|
|
@@ -68,6 +72,12 @@ Airlock MCP maps that to files:
|
|
|
68
72
|
This repo does not try to be the installed Airlock editor. It gives Codex and
|
|
69
73
|
humans a careful drafting surface before Airlock receives the final config.
|
|
70
74
|
|
|
75
|
+
`init-app-context` does not make app-local snapshots canonical. It gives an app
|
|
76
|
+
repo stable development references for coding, tests, UI planning, and generated
|
|
77
|
+
helpers. Canonical specs remain in the specs repo or installed Airlock. Use
|
|
78
|
+
`--force` to refresh snapshots from canonical workspace files after the spec
|
|
79
|
+
changes.
|
|
80
|
+
|
|
71
81
|
## Update Boundary
|
|
72
82
|
|
|
73
83
|
`self-update` supports two intentionally simple paths:
|
|
@@ -119,9 +129,10 @@ npx @reunionstudio/airlock-mcp install
|
|
|
119
129
|
|
|
120
130
|
The connector implementation belongs in `reunionstudio/airlock-mcp`. Airlock
|
|
121
131
|
MCP is the single installed interface for agents working with Airlock: building
|
|
122
|
-
specs, using specs to pull and push governed data,
|
|
123
|
-
from real use cases. This
|
|
124
|
-
|
|
132
|
+
specs, using specs to pull and push governed data, building apps or workflows
|
|
133
|
+
that use existing specs, and capturing improvements from real use cases. This
|
|
134
|
+
workbench owns the spec-building implementation: patterns, bootstrap, workspace
|
|
135
|
+
files, local checks, and the Codex skill.
|
|
125
136
|
|
|
126
137
|
That command is the agent connector/setup entry point, not the user's spec
|
|
127
138
|
workspace. After installing or registering the connector, the user still creates
|
package/docs/workflows.md
CHANGED
|
@@ -87,9 +87,13 @@ The MCP-style flow should be:
|
|
|
87
87
|
4. If Codex is creating it, choose where the repo directory should live before
|
|
88
88
|
files are written.
|
|
89
89
|
5. Tell Codex: `Use Airlock to help me improve a process with specs.`
|
|
90
|
-
6. Airlock MCP welcomes
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
6. Airlock MCP welcomes and asks which delivery mode the user wants:
|
|
91
|
+
spec-first, app-first from existing specs, or co-development.
|
|
92
|
+
7. For spec-first or co-development, Airlock MCP asks what process the user
|
|
93
|
+
wants to improve, explains the loop around the process, and proposes a small
|
|
94
|
+
first spec plus a plan for more.
|
|
95
|
+
8. For app-first or co-development, Airlock MCP identifies the app goal,
|
|
96
|
+
available specs, read specs, write specs, runtime, and approved access path.
|
|
93
97
|
|
|
94
98
|
`init-repo` creates `AGENTS.md`, `.agents/skills/airlock-mcp/SKILL.md`, and
|
|
95
99
|
`workspaces/`. That is what lets Codex understand prompts such as
|
|
@@ -130,6 +134,86 @@ current artifacts and record the divergence.
|
|
|
130
134
|
Ask for the messy version. Help turn it into a small first Airlock spec and a
|
|
131
135
|
plan for more.
|
|
132
136
|
|
|
137
|
+
## Build An App From Existing Specs
|
|
138
|
+
|
|
139
|
+
Use this when the user already has access to specs and wants to build an app,
|
|
140
|
+
dashboard, approval queue, decision UI, analysis workflow, scheduled agent, or
|
|
141
|
+
other code that reads from and submits through Airlock.
|
|
142
|
+
|
|
143
|
+
Do not bootstrap or edit a specs workspace by default. Work in the app repo
|
|
144
|
+
unless the user asks to change specs. Start by asking for:
|
|
145
|
+
|
|
146
|
+
- the app goal and decision the app should support
|
|
147
|
+
- the specs the user can access
|
|
148
|
+
- read specs, such as budgets, expenses, requests, forecasts, observations,
|
|
149
|
+
payouts, or reference data
|
|
150
|
+
- write specs, such as decisions, approvals, comments, commitments, actions, or
|
|
151
|
+
follow-ups
|
|
152
|
+
- where the app should run, such as Streamlit, web app, CLI, notebook,
|
|
153
|
+
scheduled agent, or an existing framework
|
|
154
|
+
- the available Airlock/Snowflake access path
|
|
155
|
+
- identity, evidence, timestamp, approval, and separation-of-duties needs
|
|
156
|
+
|
|
157
|
+
The app should follow the loop:
|
|
158
|
+
|
|
159
|
+
1. Observe/read governed records through approved Airlock or Snowflake surfaces.
|
|
160
|
+
2. Orient with summaries, comparisons, ranked options, recommendations,
|
|
161
|
+
exception queues, dashboards, or proposals.
|
|
162
|
+
3. Decide by capturing the human or agent choice with rationale and evidence.
|
|
163
|
+
4. Act/write by submitting the decision, approval, action, comment, or follow-up
|
|
164
|
+
through an Airlock spec contract.
|
|
165
|
+
|
|
166
|
+
Keep the boundary explicit: do not write directly to Airlock-owned tables,
|
|
167
|
+
stages, generated views, or generated tables. Do not bypass spec workflow just
|
|
168
|
+
because the app can connect to Snowflake. If no suitable decision or action spec
|
|
169
|
+
exists, explain the gap and propose a small spec-design step.
|
|
170
|
+
|
|
171
|
+
If the app repo needs local context for coding, seed it with spec snapshots and
|
|
172
|
+
a manifest:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
airlock-mcp init-app-context . --mode app-first --spec ../home-specs/workspaces/expenses
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Use `--mode co-development` when the app and specs will evolve together. Repeat
|
|
179
|
+
`--spec` for each workspace or spec JSON file the app should know about. The
|
|
180
|
+
command creates:
|
|
181
|
+
|
|
182
|
+
```text
|
|
183
|
+
airlock/
|
|
184
|
+
AGENTS.md
|
|
185
|
+
README.md
|
|
186
|
+
specs.manifest.json
|
|
187
|
+
spec-snapshots/
|
|
188
|
+
sample-records/
|
|
189
|
+
generated/
|
|
190
|
+
types/
|
|
191
|
+
sql/
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The app repo can import or generate helper code from these snapshots, but the
|
|
195
|
+
snapshots are not canonical. Canonical specs remain in the specs repo or
|
|
196
|
+
installed Airlock. Mark each manifest spec as read, write, or read_write for
|
|
197
|
+
the app before building data access and submission flows.
|
|
198
|
+
|
|
199
|
+
## Co-Develop Specs And App
|
|
200
|
+
|
|
201
|
+
Use this when the app surface and the Airlock contracts need to shape each
|
|
202
|
+
other. This is common when a dashboard, queue, approval flow, or agent workflow
|
|
203
|
+
reveals which observations, proposals, decisions, or actions need governed
|
|
204
|
+
records.
|
|
205
|
+
|
|
206
|
+
Keep two tracks visible:
|
|
207
|
+
|
|
208
|
+
- Spec track: row grain, columns, samples, attachments, access, validation,
|
|
209
|
+
workflow, and installed Airlock validation.
|
|
210
|
+
- App track: screens, reads, summaries, ranked options, decision capture,
|
|
211
|
+
governed writes, runtime, and tests.
|
|
212
|
+
|
|
213
|
+
Start with the smallest useful contract. The app may expose a thin prototype
|
|
214
|
+
against samples while the spec is being drafted, but live submissions should
|
|
215
|
+
wait for installed Airlock validation and the approved read/write path.
|
|
216
|
+
|
|
133
217
|
## Start From Feedback
|
|
134
218
|
|
|
135
219
|
Use this when the user chooses a shared feedback loop for humans and agents.
|
package/package.json
CHANGED
package/setup.py
CHANGED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from .jsonio import read_json, write_json, write_text
|
|
9
|
+
from .specs import extract_spec_config, spec_name
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
APP_CONTEXT_MODES = ("spec-first", "app-first", "co-development")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
APP_README = """# Airlock App Context
|
|
16
|
+
|
|
17
|
+
This folder is app-local context for building software that uses Airlock specs.
|
|
18
|
+
|
|
19
|
+
The canonical specs live in the specs repo or installed Airlock. Files here are
|
|
20
|
+
snapshots, samples, generated helpers, and planning context for app development.
|
|
21
|
+
Refresh them when the canonical spec changes.
|
|
22
|
+
|
|
23
|
+
Use:
|
|
24
|
+
|
|
25
|
+
- `specs.manifest.json` to track which specs the app reads from or writes to.
|
|
26
|
+
- `spec-snapshots/` for app-local copies of spec configs.
|
|
27
|
+
- `sample-records/` for app-local sample records used in UI and test fixtures.
|
|
28
|
+
- `generated/types/` for generated application types.
|
|
29
|
+
- `generated/sql/` for reviewed SQL helpers or query templates.
|
|
30
|
+
|
|
31
|
+
Do not store credentials here. Do not write directly to Airlock-owned tables,
|
|
32
|
+
stages, generated views, or generated tables. Use approved Airlock/Snowflake
|
|
33
|
+
access paths and submit governed decisions or actions through spec contracts.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
APP_AGENTS = """# Airlock App Guidance
|
|
38
|
+
|
|
39
|
+
This repo may contain application code that uses Airlock specs.
|
|
40
|
+
|
|
41
|
+
## Modes
|
|
42
|
+
|
|
43
|
+
- Spec-first: design governed specs before building the app surface.
|
|
44
|
+
- App-first: use existing specs to build an app, dashboard, queue, or workflow.
|
|
45
|
+
- Co-development: evolve the app and specs together while keeping the contract
|
|
46
|
+
explicit.
|
|
47
|
+
|
|
48
|
+
## App Context
|
|
49
|
+
|
|
50
|
+
The `airlock/` folder is app-local. Its spec snapshots are references for
|
|
51
|
+
development, not canonical specs. Canonical specs live in the specs repo or
|
|
52
|
+
installed Airlock.
|
|
53
|
+
|
|
54
|
+
Keep two tracks visible during co-development:
|
|
55
|
+
|
|
56
|
+
- Spec track: row grain, columns, samples, access, validation, and workflow.
|
|
57
|
+
- App track: screens, reads, decisions, writes, user actions, and runtime.
|
|
58
|
+
|
|
59
|
+
Use approved Airlock/Snowflake access paths. Do not bypass spec workflow or
|
|
60
|
+
write directly to Airlock-owned tables, stages, generated views, or generated
|
|
61
|
+
tables. If the app needs to submit a decision or action and no suitable write
|
|
62
|
+
spec exists, propose a small spec-design step.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(frozen=True)
|
|
67
|
+
class AppContextResult:
|
|
68
|
+
root: Path
|
|
69
|
+
mode: str
|
|
70
|
+
created: tuple[Path, ...]
|
|
71
|
+
updated: tuple[Path, ...]
|
|
72
|
+
kept: tuple[Path, ...]
|
|
73
|
+
specs: tuple[str, ...]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _safe_name(value: str) -> str:
|
|
77
|
+
cleaned = re.sub(r"[^a-zA-Z0-9_.-]+", "_", value.strip())
|
|
78
|
+
cleaned = cleaned.strip("._-").lower()
|
|
79
|
+
return cleaned or "spec"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _write_text_if_needed(path: Path, text: str, *, force: bool) -> str:
|
|
83
|
+
if path.exists() and not force:
|
|
84
|
+
return "kept"
|
|
85
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
write_text(path, text, force=True)
|
|
87
|
+
return "created"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _write_json_if_needed(path: Path, value: Any, *, force: bool) -> str:
|
|
91
|
+
if path.exists() and not force:
|
|
92
|
+
return "kept"
|
|
93
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
94
|
+
write_json(path, value, force=True)
|
|
95
|
+
return "created"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _load_spec_source(source: Path) -> tuple[dict[str, Any], dict[str, Any] | None]:
|
|
99
|
+
if source.is_dir():
|
|
100
|
+
raw_spec = read_json(source / "spec.config.json")
|
|
101
|
+
if not isinstance(raw_spec, dict):
|
|
102
|
+
raise ValueError(f"invalid spec workspace: {source}")
|
|
103
|
+
raw_sample = read_json(source / "sample.records.json")
|
|
104
|
+
return raw_spec, raw_sample if isinstance(raw_sample, dict) else None
|
|
105
|
+
|
|
106
|
+
raw = read_json(source)
|
|
107
|
+
if raw is None:
|
|
108
|
+
raise FileNotFoundError(source)
|
|
109
|
+
return extract_spec_config(raw), None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _manifest(mode: str, entries: list[dict[str, Any]]) -> dict[str, Any]:
|
|
113
|
+
return {
|
|
114
|
+
"schema_version": 1,
|
|
115
|
+
"mode": mode,
|
|
116
|
+
"canonical_source": "specs repo or installed Airlock",
|
|
117
|
+
"snapshot_policy": "Snapshots are app-local development references, not canonical specs.",
|
|
118
|
+
"specs": entries,
|
|
119
|
+
"tracks": {
|
|
120
|
+
"spec_track": "row grain, columns, samples, access, validation, workflow",
|
|
121
|
+
"app_track": "screens, reads, decisions, writes, user actions, runtime",
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _merge_manifest(existing: Any, new_manifest: dict[str, Any]) -> dict[str, Any]:
|
|
127
|
+
if not isinstance(existing, dict):
|
|
128
|
+
return new_manifest
|
|
129
|
+
|
|
130
|
+
merged = dict(existing)
|
|
131
|
+
merged.update(
|
|
132
|
+
{
|
|
133
|
+
"schema_version": new_manifest["schema_version"],
|
|
134
|
+
"mode": new_manifest["mode"],
|
|
135
|
+
"canonical_source": new_manifest["canonical_source"],
|
|
136
|
+
"snapshot_policy": new_manifest["snapshot_policy"],
|
|
137
|
+
"tracks": new_manifest["tracks"],
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
existing_entries = existing.get("specs")
|
|
142
|
+
by_name: dict[str, dict[str, Any]] = {}
|
|
143
|
+
if isinstance(existing_entries, list):
|
|
144
|
+
for entry in existing_entries:
|
|
145
|
+
if isinstance(entry, dict) and isinstance(entry.get("spec_name"), str):
|
|
146
|
+
by_name[entry["spec_name"]] = dict(entry)
|
|
147
|
+
|
|
148
|
+
for entry in new_manifest["specs"]:
|
|
149
|
+
previous = by_name.get(entry["spec_name"], {})
|
|
150
|
+
merged_entry = {**previous, **entry}
|
|
151
|
+
if previous.get("role") and entry.get("role") == "unknown":
|
|
152
|
+
merged_entry["role"] = previous["role"]
|
|
153
|
+
by_name[entry["spec_name"]] = merged_entry
|
|
154
|
+
|
|
155
|
+
merged["specs"] = list(by_name.values())
|
|
156
|
+
return merged
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def init_app_context(
|
|
160
|
+
target: Path,
|
|
161
|
+
*,
|
|
162
|
+
mode: str = "app-first",
|
|
163
|
+
spec_sources: list[Path] | None = None,
|
|
164
|
+
force: bool = False,
|
|
165
|
+
) -> AppContextResult:
|
|
166
|
+
if mode not in APP_CONTEXT_MODES:
|
|
167
|
+
raise ValueError(f"mode must be one of: {', '.join(APP_CONTEXT_MODES)}")
|
|
168
|
+
|
|
169
|
+
root = target.expanduser().resolve()
|
|
170
|
+
if root.exists() and not root.is_dir():
|
|
171
|
+
raise NotADirectoryError(str(root))
|
|
172
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
173
|
+
|
|
174
|
+
airlock_root = root / "airlock"
|
|
175
|
+
created: list[Path] = []
|
|
176
|
+
updated: list[Path] = []
|
|
177
|
+
kept: list[Path] = []
|
|
178
|
+
|
|
179
|
+
for directory in (
|
|
180
|
+
airlock_root,
|
|
181
|
+
airlock_root / "spec-snapshots",
|
|
182
|
+
airlock_root / "sample-records",
|
|
183
|
+
airlock_root / "generated",
|
|
184
|
+
airlock_root / "generated" / "types",
|
|
185
|
+
airlock_root / "generated" / "sql",
|
|
186
|
+
):
|
|
187
|
+
if directory.exists():
|
|
188
|
+
kept.append(directory)
|
|
189
|
+
else:
|
|
190
|
+
directory.mkdir(parents=True)
|
|
191
|
+
created.append(directory)
|
|
192
|
+
|
|
193
|
+
entries: list[dict[str, Any]] = []
|
|
194
|
+
spec_names: list[str] = []
|
|
195
|
+
for source in spec_sources or []:
|
|
196
|
+
spec_config, sample_records = _load_spec_source(source.expanduser())
|
|
197
|
+
name = spec_name(spec_config)
|
|
198
|
+
safe = _safe_name(name)
|
|
199
|
+
snapshot_path = airlock_root / "spec-snapshots" / f"{safe}.spec.config.json"
|
|
200
|
+
bucket = created if _write_json_if_needed(snapshot_path, spec_config, force=force) == "created" else kept
|
|
201
|
+
bucket.append(snapshot_path)
|
|
202
|
+
|
|
203
|
+
sample_relative: str | None = None
|
|
204
|
+
if sample_records is not None:
|
|
205
|
+
sample_path = airlock_root / "sample-records" / f"{safe}.sample.records.json"
|
|
206
|
+
bucket = created if _write_json_if_needed(sample_path, sample_records, force=force) == "created" else kept
|
|
207
|
+
bucket.append(sample_path)
|
|
208
|
+
sample_relative = str(sample_path.relative_to(root))
|
|
209
|
+
|
|
210
|
+
core = spec_config.get("core_config")
|
|
211
|
+
core = core if isinstance(core, dict) else {}
|
|
212
|
+
entries.append(
|
|
213
|
+
{
|
|
214
|
+
"spec_name": name,
|
|
215
|
+
"spec_alias": core.get("spec_alias"),
|
|
216
|
+
"source": str(source),
|
|
217
|
+
"snapshot": str(snapshot_path.relative_to(root)),
|
|
218
|
+
"sample_records": sample_relative,
|
|
219
|
+
"role": "unknown",
|
|
220
|
+
"snapshot_only": True,
|
|
221
|
+
}
|
|
222
|
+
)
|
|
223
|
+
spec_names.append(name)
|
|
224
|
+
|
|
225
|
+
for path, content in (
|
|
226
|
+
(airlock_root / "README.md", APP_README),
|
|
227
|
+
(airlock_root / "AGENTS.md", APP_AGENTS),
|
|
228
|
+
(airlock_root / "generated" / "types" / ".gitkeep", ""),
|
|
229
|
+
(airlock_root / "generated" / "sql" / ".gitkeep", ""),
|
|
230
|
+
):
|
|
231
|
+
bucket = created if _write_text_if_needed(path, content, force=force) == "created" else kept
|
|
232
|
+
bucket.append(path)
|
|
233
|
+
|
|
234
|
+
manifest_path = airlock_root / "specs.manifest.json"
|
|
235
|
+
manifest = _manifest(mode, entries)
|
|
236
|
+
if manifest_path.exists() and not force:
|
|
237
|
+
write_json(manifest_path, _merge_manifest(read_json(manifest_path), manifest), force=True)
|
|
238
|
+
updated.append(manifest_path)
|
|
239
|
+
else:
|
|
240
|
+
bucket = created if _write_json_if_needed(manifest_path, manifest, force=force) == "created" else kept
|
|
241
|
+
bucket.append(manifest_path)
|
|
242
|
+
|
|
243
|
+
return AppContextResult(
|
|
244
|
+
root=airlock_root,
|
|
245
|
+
mode=mode,
|
|
246
|
+
created=tuple(created),
|
|
247
|
+
updated=tuple(updated),
|
|
248
|
+
kept=tuple(kept),
|
|
249
|
+
specs=tuple(spec_names),
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def format_app_context_result(result: AppContextResult) -> str:
|
|
254
|
+
lines = [f"initialized app context {result.root}", f"mode: {result.mode}"]
|
|
255
|
+
for path in result.created:
|
|
256
|
+
lines.append(f"created {path.relative_to(result.root.parent)}")
|
|
257
|
+
for path in result.updated:
|
|
258
|
+
lines.append(f"updated {path.relative_to(result.root.parent)}")
|
|
259
|
+
for path in result.kept:
|
|
260
|
+
lines.append(f"kept {path.relative_to(result.root.parent)}")
|
|
261
|
+
lines.append("seeded_specs: " + (", ".join(result.specs) if result.specs else "none"))
|
|
262
|
+
lines.extend(
|
|
263
|
+
[
|
|
264
|
+
"",
|
|
265
|
+
"next:",
|
|
266
|
+
"1. Treat spec snapshots as app-local references, not canonical specs.",
|
|
267
|
+
"2. Mark each manifest spec as read, write, or read_write for the app.",
|
|
268
|
+
"3. Build app reads and governed submissions through approved Airlock/Snowflake access paths.",
|
|
269
|
+
]
|
|
270
|
+
)
|
|
271
|
+
return "\n".join(lines)
|
package/src/airlock_mcp/art.py
CHANGED
|
@@ -21,6 +21,6 @@ def about_text() -> str:
|
|
|
21
21
|
return f"""{MCP_MARK.rstrip()}
|
|
22
22
|
|
|
23
23
|
version: {__version__}
|
|
24
|
-
commands: init-repo, init, import-spec, clone, rename, archive, restore, list-workspaces, summary, next, check, export-csv, render-sql, self-update
|
|
24
|
+
commands: init-repo, init-app-context, init, import-spec, clone, rename, archive, restore, list-workspaces, summary, next, check, export-csv, render-sql, self-update
|
|
25
25
|
principle: Codex is the conversation; files are memory; Airlock is authority.
|
|
26
26
|
"""
|
|
@@ -16,8 +16,9 @@ This repo is an Airlock MCP workspace for drafting Airlock specs with Codex.
|
|
|
16
16
|
The public install surface is Airlock MCP, for example
|
|
17
17
|
`npx @reunionstudio/airlock-mcp install`. Airlock MCP is the single installed
|
|
18
18
|
interface for building specs, using specs, pulling and pushing governed data,
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
building apps or workflows that use existing specs, and discovering
|
|
20
|
+
improvements from real use cases. This project repo remains the durable memory
|
|
21
|
+
for the specs being drafted.
|
|
21
22
|
|
|
22
23
|
## Repo Naming
|
|
23
24
|
|
|
@@ -45,7 +46,13 @@ installed Airlock app.
|
|
|
45
46
|
## Starting A Spec Project
|
|
46
47
|
|
|
47
48
|
After bootstrap, welcome the user and orient before creating a workspace. Start
|
|
48
|
-
|
|
49
|
+
by asking which delivery mode they want:
|
|
50
|
+
|
|
51
|
+
1. Spec-first: design governed specs before building the app surface.
|
|
52
|
+
2. App-first: build from existing specs.
|
|
53
|
+
3. Co-development: develop the app and specs together.
|
|
54
|
+
|
|
55
|
+
For spec-first or co-development work, ask:
|
|
49
56
|
|
|
50
57
|
What process do you want to improve?
|
|
51
58
|
|
|
@@ -79,10 +86,48 @@ plan for more. Do not create the first workspace until the user chooses a path.
|
|
|
79
86
|
Create `posts` only when the user wants a shared feedback loop or explicitly
|
|
80
87
|
asks for the posts pattern.
|
|
81
88
|
|
|
89
|
+
## Building Apps With Existing Specs
|
|
90
|
+
|
|
91
|
+
Use this path when the user wants to build an app, dashboard, approval queue,
|
|
92
|
+
decision UI, analysis workflow, scheduled agent, or other code that uses specs
|
|
93
|
+
they already have access to. Treat this as app/workflow implementation, not
|
|
94
|
+
spec editing, unless the user explicitly asks to change specs.
|
|
95
|
+
|
|
96
|
+
Use `airlock-mcp init-app-context` in an app repo when the app needs local
|
|
97
|
+
Airlock context. It creates `airlock/specs.manifest.json`,
|
|
98
|
+
`airlock/spec-snapshots/`, `airlock/sample-records/`, and generated helper
|
|
99
|
+
folders. These files are app-local references, not canonical specs. Canonical
|
|
100
|
+
specs live in the specs repo or installed Airlock.
|
|
101
|
+
|
|
102
|
+
Identify:
|
|
103
|
+
|
|
104
|
+
- the app goal and decision the app should support
|
|
105
|
+
- read specs such as budgets, expenses, requests, forecasts, observations,
|
|
106
|
+
payouts, or reference data
|
|
107
|
+
- write specs such as decisions, approvals, comments, commitments, actions, or
|
|
108
|
+
follow-ups
|
|
109
|
+
- where the app should run, such as Streamlit, web app, CLI, notebook,
|
|
110
|
+
scheduled agent, or existing app framework
|
|
111
|
+
- the Airlock access path available in this environment
|
|
112
|
+
- identity, evidence, timestamp, approval, and separation-of-duties rules
|
|
113
|
+
|
|
114
|
+
The app may orient the user with summaries, comparisons, rankings, exception
|
|
115
|
+
queues, proposals, or dashboards. It should submit governed choices back
|
|
116
|
+
through an Airlock spec contract. Do not write directly to Airlock-owned tables,
|
|
117
|
+
stages, generated views, or generated tables. Do not bypass spec workflow just
|
|
118
|
+
because the app can connect to Snowflake. If no suitable decision or action spec
|
|
119
|
+
exists, explain the gap and propose a small spec-design step.
|
|
120
|
+
|
|
121
|
+
In co-development mode, keep two tracks visible:
|
|
122
|
+
|
|
123
|
+
- Spec track: row grain, columns, samples, access, validation, workflow
|
|
124
|
+
- App track: screens, reads, decisions, writes, user actions, runtime
|
|
125
|
+
|
|
82
126
|
## Working Style
|
|
83
127
|
|
|
84
128
|
- Use the repo-scoped `$airlock-mcp` skill for spec drafting, review, and
|
|
85
|
-
pattern selection
|
|
129
|
+
pattern selection, or for app/workflow implementation that uses existing
|
|
130
|
+
Airlock specs.
|
|
86
131
|
- Keep drafts small and concrete. Prefer one useful governed output over a
|
|
87
132
|
large speculative process map, then keep a plan for later specs.
|
|
88
133
|
- Preserve decisions in workspace files so future Codex sessions can resume
|
package/src/airlock_mcp/cli.py
CHANGED
|
@@ -5,6 +5,7 @@ import sys
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
from . import __version__
|
|
8
|
+
from .app_context import APP_CONTEXT_MODES, format_app_context_result, init_app_context
|
|
8
9
|
from .art import about_text
|
|
9
10
|
from .bootstrap import bootstrap_repo, format_bootstrap_result
|
|
10
11
|
from .jsonio import read_json
|
|
@@ -64,6 +65,17 @@ def init_repo(args: argparse.Namespace) -> int:
|
|
|
64
65
|
return 0
|
|
65
66
|
|
|
66
67
|
|
|
68
|
+
def init_app(args: argparse.Namespace) -> int:
|
|
69
|
+
result = init_app_context(
|
|
70
|
+
Path(args.path),
|
|
71
|
+
mode=args.mode,
|
|
72
|
+
spec_sources=[Path(source) for source in args.spec],
|
|
73
|
+
force=args.force,
|
|
74
|
+
)
|
|
75
|
+
print(format_app_context_result(result))
|
|
76
|
+
return 0
|
|
77
|
+
|
|
78
|
+
|
|
67
79
|
def init_workspace(args: argparse.Namespace) -> int:
|
|
68
80
|
patterns = load_patterns(repo_root())
|
|
69
81
|
pattern = patterns[args.pattern]
|
|
@@ -308,6 +320,26 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
308
320
|
init_repo_parser.add_argument("--force", action="store_true", help="Overwrite AGENTS.md and the repo-scoped skill.")
|
|
309
321
|
init_repo_parser.set_defaults(func=init_repo)
|
|
310
322
|
|
|
323
|
+
init_app_parser = subparsers.add_parser(
|
|
324
|
+
"init-app-context",
|
|
325
|
+
help="Prepare an app repo airlock/ context with spec snapshots and a manifest.",
|
|
326
|
+
)
|
|
327
|
+
init_app_parser.add_argument("path", nargs="?", default=".", help="App repo path. Defaults to current directory.")
|
|
328
|
+
init_app_parser.add_argument(
|
|
329
|
+
"--mode",
|
|
330
|
+
choices=APP_CONTEXT_MODES,
|
|
331
|
+
default="app-first",
|
|
332
|
+
help="Development mode. Defaults to app-first.",
|
|
333
|
+
)
|
|
334
|
+
init_app_parser.add_argument(
|
|
335
|
+
"--spec",
|
|
336
|
+
action="append",
|
|
337
|
+
default=[],
|
|
338
|
+
help="Spec workspace directory or spec JSON file to snapshot. Repeat for multiple specs.",
|
|
339
|
+
)
|
|
340
|
+
init_app_parser.add_argument("--force", action="store_true", help="Overwrite app context files.")
|
|
341
|
+
init_app_parser.set_defaults(func=init_app)
|
|
342
|
+
|
|
311
343
|
init_parser = subparsers.add_parser("init", help="Create a spec workspace.")
|
|
312
344
|
init_parser.add_argument("name", help="Workspace folder name.")
|
|
313
345
|
init_parser.add_argument(
|
package/src/mcp.mjs
CHANGED
|
@@ -7,7 +7,7 @@ const GETTING_STARTED_URI = "airlock://getting-started";
|
|
|
7
7
|
|
|
8
8
|
const START_TOOL = {
|
|
9
9
|
name: "airlock_start",
|
|
10
|
-
description: "Return Airlock MCP setup guidance for
|
|
10
|
+
description: "Return Airlock MCP setup guidance for specs and apps that use specs.",
|
|
11
11
|
inputSchema: {
|
|
12
12
|
type: "object",
|
|
13
13
|
properties: {
|
|
@@ -44,10 +44,10 @@ export function handleMcpRequest(message) {
|
|
|
44
44
|
},
|
|
45
45
|
serverInfo: {
|
|
46
46
|
name: "airlock",
|
|
47
|
-
version: "0.1.
|
|
47
|
+
version: "0.1.3",
|
|
48
48
|
},
|
|
49
49
|
instructions:
|
|
50
|
-
"Airlock MCP helps agents improve processes with Airlock specs. Use airlock_start for orientation or the airlock_* tools to bootstrap, draft, check, summarize, export, and render specs.",
|
|
50
|
+
"Airlock MCP helps agents improve processes with Airlock specs and build apps or workflows that use existing specs. Use airlock_start for orientation or the airlock_* tools to bootstrap, draft, check, summarize, export, and render specs.",
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -84,7 +84,7 @@ export function handleMcpRequest(message) {
|
|
|
84
84
|
{
|
|
85
85
|
name: "airlock-start",
|
|
86
86
|
title: "Start Airlock",
|
|
87
|
-
description: "Bootstrap a Git-backed specs repo
|
|
87
|
+
description: "Bootstrap a Git-backed specs repo or choose an Airlock app/spec path.",
|
|
88
88
|
arguments: [
|
|
89
89
|
{
|
|
90
90
|
name: "project",
|
|
@@ -103,7 +103,7 @@ export function handleMcpRequest(message) {
|
|
|
103
103
|
}
|
|
104
104
|
const project = params?.arguments?.project || "Home";
|
|
105
105
|
return makeResponse(id, {
|
|
106
|
-
description: "Start building
|
|
106
|
+
description: "Start building specs or apps that use Airlock specs.",
|
|
107
107
|
messages: [
|
|
108
108
|
{
|
|
109
109
|
role: "user",
|
package/src/text.mjs
CHANGED
|
@@ -25,19 +25,23 @@ Set up this project as an Airlock specs repo. If this project is not already a
|
|
|
25
25
|
Git repo, recommend storing it in version control, preferably GitHub if that is
|
|
26
26
|
available. If you are creating the repo for me, ask where the ${repoName}
|
|
27
27
|
directory should live before making it, then offer to initialize git and create
|
|
28
|
-
or push a GitHub repo.
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
or push a GitHub repo. If I am in an app repo and want to build software that
|
|
29
|
+
uses existing specs, or develop the app and specs together, do not bootstrap a
|
|
30
|
+
specs repo unless I ask for spec edits.
|
|
31
|
+
|
|
32
|
+
Welcome me by asking whether I want spec-first, app-first, or co-development:
|
|
33
|
+
spec-first designs governed specs before the app, app-first builds from existing
|
|
34
|
+
specs, and co-development evolves the app and specs together. For spec-first or
|
|
35
|
+
co-development, ask what process I want to improve. Explain that Airlock works
|
|
31
36
|
best when we can identify the loop around that process: what information comes
|
|
32
37
|
in, what context helps us understand it, what decision needs to be made, and
|
|
33
38
|
what action happens after the decision. Information may come from apps, files,
|
|
34
|
-
forms, people,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
redact secrets.
|
|
39
|
+
forms, people, emails, calls, mail, websites, APIs, data feeds, or physical
|
|
40
|
+
events. Actions may go back through those same places. Ask whether I already
|
|
41
|
+
have artifacts: CSV or Excel files, JSON samples, API docs, schemas, forms,
|
|
42
|
+
screenshots, PDFs, exports, message examples, or other content people already
|
|
43
|
+
use. Treat a small real sample as stronger evidence than a long explanation,
|
|
44
|
+
and remind me to redact secrets.
|
|
41
45
|
|
|
42
46
|
When useful, check the reusable airlock-specs library for starting points,
|
|
43
47
|
patterns, and ideas, but do not assume those library specs match current
|
|
@@ -45,7 +49,15 @@ third-party systems. Prefer current API docs, real exports, samples, and other
|
|
|
45
49
|
artifacts when they conflict with a library shape.
|
|
46
50
|
|
|
47
51
|
Ask for the messy version, then help turn it into a small first Airlock spec
|
|
48
|
-
and a plan for more. Do not create the first workspace until I choose a path
|
|
52
|
+
and a plan for more. Do not create the first workspace until I choose a path.
|
|
53
|
+
|
|
54
|
+
If I want app-first or co-development, ask for the app goal, the specs I can
|
|
55
|
+
access, which specs are read sources, which spec records decisions or actions,
|
|
56
|
+
and where the app should run. Offer to run \`airlock-mcp init-app-context\` in
|
|
57
|
+
the app repo to seed \`airlock/specs.manifest.json\`, spec snapshots, sample
|
|
58
|
+
records, and generated helper folders. Help code the app using approved
|
|
59
|
+
Airlock/Snowflake access paths. Do not write directly to Airlock-owned tables
|
|
60
|
+
or bypass spec workflow.`;
|
|
49
61
|
}
|
|
50
62
|
|
|
51
63
|
export function nextSteps(project) {
|
|
@@ -63,8 +75,11 @@ ${airlockPrompt(project)
|
|
|
63
75
|
|
|
64
76
|
Airlock MCP will offer:
|
|
65
77
|
- process discovery before choosing a spec pattern
|
|
78
|
+
- spec-first, app-first, and co-development planning
|
|
66
79
|
- spec design with the bundled workbench
|
|
67
80
|
- Airlock operating patterns for OODA loops and separation of duties
|
|
81
|
+
- app context seeding with spec snapshots and manifests
|
|
82
|
+
- app and workflow coding against existing Airlock specs
|
|
68
83
|
- observe specs for controlled interface ingestion
|
|
69
84
|
- orient specs for proposals, context, scoring, or exception queues
|
|
70
85
|
- decision specs and action specs for governed follow-through
|
|
@@ -77,16 +92,19 @@ export function gettingStartedText(project) {
|
|
|
77
92
|
|
|
78
93
|
Airlock MCP is the single installed interface for AI agents working with
|
|
79
94
|
Airlock. It helps a person and their agent improve processes by designing
|
|
80
|
-
specs, using specs for governed data movement,
|
|
81
|
-
|
|
95
|
+
specs, using specs for governed data movement, planning OODA loops, and building
|
|
96
|
+
apps or workflows that read from and submit through existing specs.
|
|
82
97
|
|
|
83
|
-
Airlock MCP gives agents
|
|
98
|
+
Airlock MCP gives agents three kinds of Airlock help:
|
|
84
99
|
|
|
85
100
|
1. Spec design: draft, check, revise, import, clone, and prepare specs for
|
|
86
101
|
installed Airlock validation.
|
|
87
102
|
2. Airlock operating patterns: use specs to organize observations, orientation,
|
|
88
103
|
governed decisions, controlled actions, separation of duties, and feedback
|
|
89
104
|
loops.
|
|
105
|
+
3. App and workflow implementation: build dashboards, queues, decision UIs,
|
|
106
|
+
analyses, and agent workflows that use existing specs without bypassing
|
|
107
|
+
Airlock contracts.
|
|
90
108
|
|
|
91
109
|
Start in a Git-backed specs repo such as ${specsRepoName(project)}. GitHub is
|
|
92
110
|
the recommended default when the user has it set up, but any normal repository
|
|
@@ -98,7 +116,13 @@ Use this prompt in the specs repo:
|
|
|
98
116
|
|
|
99
117
|
${airlockPrompt(project)}
|
|
100
118
|
|
|
101
|
-
First ask
|
|
119
|
+
First ask which delivery mode the user wants:
|
|
120
|
+
|
|
121
|
+
1. Spec-first: design governed specs before building the app surface.
|
|
122
|
+
2. App-first: build an app or workflow from existing specs.
|
|
123
|
+
3. Co-development: develop the app and specs together.
|
|
124
|
+
|
|
125
|
+
For spec-first and co-development work, ask: What process do you want to improve?
|
|
102
126
|
|
|
103
127
|
Airlock works best when we can identify the loop around a process:
|
|
104
128
|
|
|
@@ -123,7 +147,15 @@ current shape of any third-party system. Prefer current API docs, real exports,
|
|
|
123
147
|
samples, and other artifacts when they conflict with the library.
|
|
124
148
|
|
|
125
149
|
Give Codex the messy version of the process. Airlock MCP should help turn it
|
|
126
|
-
into a small first Airlock spec and a plan for more
|
|
150
|
+
into a small first Airlock spec and a plan for more.
|
|
151
|
+
|
|
152
|
+
For app-first and co-development work, give Codex the app goal and available
|
|
153
|
+
specs. Airlock MCP should identify read specs, write specs, orienting views,
|
|
154
|
+
decision capture, and safe Airlock/Snowflake access paths. It can seed an app
|
|
155
|
+
repo with \`airlock/specs.manifest.json\`, spec snapshots, sample records, and
|
|
156
|
+
generated helper folders. The app should submit decisions, approvals, actions,
|
|
157
|
+
comments, or follow-ups through Airlock spec contracts, not direct table writes.
|
|
158
|
+
In co-development mode, keep the spec track and app track visible side by side.`;
|
|
127
159
|
}
|
|
128
160
|
|
|
129
161
|
export function helpText() {
|
|
@@ -148,5 +180,7 @@ Airlock MCP is the single installed interface for agents working with Airlock.
|
|
|
148
180
|
Spec building is bundled inside that experience.
|
|
149
181
|
Airlock operating patterns help connect specs into OODA loops, separation of
|
|
150
182
|
duties, governed decisions, controlled actions, and feedback loops.
|
|
183
|
+
Airlock MCP can also help build apps and workflows that use existing specs for
|
|
184
|
+
approved reads and governed submissions.
|
|
151
185
|
`;
|
|
152
186
|
}
|
package/src/workbench.mjs
CHANGED
|
@@ -23,6 +23,8 @@ const workspaceProperty = {
|
|
|
23
23
|
maxLength: MAX_ARG_LENGTH,
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
const appModeValues = ["spec-first", "app-first", "co-development"];
|
|
27
|
+
|
|
26
28
|
export const WORKBENCH_TOOLS = [
|
|
27
29
|
{
|
|
28
30
|
name: "airlock_doctor",
|
|
@@ -42,6 +44,32 @@ export const WORKBENCH_TOOLS = [
|
|
|
42
44
|
cwd: cwdProperty,
|
|
43
45
|
}),
|
|
44
46
|
},
|
|
47
|
+
{
|
|
48
|
+
name: "airlock_init_app_context",
|
|
49
|
+
description: "Seed an app repo with airlock/ spec snapshots, samples, generated placeholders, and a manifest.",
|
|
50
|
+
inputSchema: objectSchema({
|
|
51
|
+
path: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "App repo path to initialize. Defaults to the current directory.",
|
|
54
|
+
maxLength: MAX_ARG_LENGTH,
|
|
55
|
+
},
|
|
56
|
+
mode: {
|
|
57
|
+
type: "string",
|
|
58
|
+
enum: appModeValues,
|
|
59
|
+
description: "Development mode. Defaults to app-first.",
|
|
60
|
+
},
|
|
61
|
+
specs: {
|
|
62
|
+
type: "array",
|
|
63
|
+
items: {
|
|
64
|
+
type: "string",
|
|
65
|
+
maxLength: MAX_ARG_LENGTH,
|
|
66
|
+
},
|
|
67
|
+
description: "Spec workspace directories or spec JSON files to snapshot.",
|
|
68
|
+
},
|
|
69
|
+
force: forceProperty,
|
|
70
|
+
cwd: cwdProperty,
|
|
71
|
+
}),
|
|
72
|
+
},
|
|
45
73
|
{
|
|
46
74
|
name: "airlock_list_patterns",
|
|
47
75
|
description: "List bundled Airlock spec starter patterns.",
|
|
@@ -230,6 +258,28 @@ function optionalEnum(args, name, allowed, fallback) {
|
|
|
230
258
|
return value;
|
|
231
259
|
}
|
|
232
260
|
|
|
261
|
+
function optionalStringArray(args, name) {
|
|
262
|
+
const value = args?.[name];
|
|
263
|
+
if (value === undefined || value === null) {
|
|
264
|
+
return [];
|
|
265
|
+
}
|
|
266
|
+
if (!Array.isArray(value)) {
|
|
267
|
+
throw new Error(`${name} must be an array`);
|
|
268
|
+
}
|
|
269
|
+
return value.map((entry, index) => {
|
|
270
|
+
if (typeof entry !== "string") {
|
|
271
|
+
throw new Error(`${name}[${index}] must be a string`);
|
|
272
|
+
}
|
|
273
|
+
if (entry.length > MAX_ARG_LENGTH || /[\u0000-\u001f\u007f]/.test(entry)) {
|
|
274
|
+
throw new Error(`${name}[${index}] must be ${MAX_ARG_LENGTH} characters or fewer with no control characters`);
|
|
275
|
+
}
|
|
276
|
+
if (entry.startsWith("-")) {
|
|
277
|
+
throw new Error(`${name}[${index}] must not start with '-'`);
|
|
278
|
+
}
|
|
279
|
+
return entry;
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
233
283
|
function requiredEnum(args, name, allowed) {
|
|
234
284
|
const value = optionalEnum(args, name, allowed, undefined);
|
|
235
285
|
if (value === undefined) {
|
|
@@ -264,6 +314,19 @@ function cliArgsForTool(name, args = {}) {
|
|
|
264
314
|
if (force) cliArgs.push("--force");
|
|
265
315
|
return { cwd, cliArgs };
|
|
266
316
|
}
|
|
317
|
+
case "airlock_init_app_context": {
|
|
318
|
+
const cliArgs = [
|
|
319
|
+
"init-app-context",
|
|
320
|
+
optionalString(args, "path", "."),
|
|
321
|
+
"--mode",
|
|
322
|
+
optionalEnum(args, "mode", appModeValues, "app-first"),
|
|
323
|
+
];
|
|
324
|
+
for (const spec of optionalStringArray(args, "specs")) {
|
|
325
|
+
cliArgs.push("--spec", spec);
|
|
326
|
+
}
|
|
327
|
+
if (force) cliArgs.push("--force");
|
|
328
|
+
return { cwd, cliArgs };
|
|
329
|
+
}
|
|
267
330
|
case "airlock_list_patterns":
|
|
268
331
|
return { cwd, cliArgs: ["list-patterns"] };
|
|
269
332
|
case "airlock_show_pattern": {
|