@reunionstudio/airlock-mcp 0.1.0 → 0.1.2
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 +84 -16
- package/.agents/skills/airlock-mcp/agents/openai.yaml +2 -2
- package/README.md +42 -20
- package/docs/architecture.md +8 -7
- package/docs/install-surface.md +16 -7
- package/docs/ooda-loop.md +31 -8
- package/docs/spec-workbench-architecture.md +3 -2
- package/docs/workflows.md +77 -20
- package/package.json +1 -1
- package/setup.py +1 -1
- package/src/airlock_mcp/__init__.py +1 -1
- package/src/airlock_mcp/bootstrap.py +48 -11
- package/src/airlock_mcp/cli.py +1 -1
- package/src/airlock_mcp/manage.py +18 -2
- package/src/airlock_mcp/summary.py +215 -23
- package/src/mcp.mjs +4 -4
- package/src/text.mjs +77 -33
- package/src/workbench.mjs +1 -1
|
@@ -20,6 +20,12 @@ the deterministic checker.
|
|
|
20
20
|
organization name and suggest `<slug>-specs`; for example, `Home` becomes
|
|
21
21
|
`home-specs`. Reserve `airlock-specs` for the canonical reusable spec
|
|
22
22
|
library.
|
|
23
|
+
Recommend storing the specs project in a real version-controlled repo,
|
|
24
|
+
preferably a GitHub repo when the user already uses GitHub. Before creating
|
|
25
|
+
the directory, ask where the user wants the `<slug>-specs` repo to live. If
|
|
26
|
+
Codex can create the repo, offer to initialize git and create/push a GitHub
|
|
27
|
+
repo; otherwise ask the user to create the repo and open it in Codex before
|
|
28
|
+
running `airlock-mcp init-repo`.
|
|
23
29
|
Public users should normally arrive through the single Airlock MCP install,
|
|
24
30
|
then ask Airlock to help build and use specs. Treat spec-building as a
|
|
25
31
|
capability inside that experience, not a second install.
|
|
@@ -29,28 +35,53 @@ the deterministic checker.
|
|
|
29
35
|
any workspace.
|
|
30
36
|
If the user opened the public `airlock-mcp` workbench repo as the entry
|
|
31
37
|
point, help them create a separate `<slug>-specs` project repo beside it.
|
|
32
|
-
3. If no workspace exists, welcome the user and
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
+
3. If no workspace exists, welcome the user and ask:
|
|
39
|
+
`What process do you want to improve?`
|
|
40
|
+
Explain that Airlock works best when we can identify the loop around that
|
|
41
|
+
process:
|
|
42
|
+
- what information comes in
|
|
43
|
+
- what context helps people or agents understand it
|
|
44
|
+
- what decision needs to be made
|
|
45
|
+
- what action happens after the decision
|
|
46
|
+
Give examples before naming the concept: apps, files, forms, people, emails,
|
|
47
|
+
calls, mail, websites, APIs, data feeds, or physical events. Then call these
|
|
48
|
+
places interfaces: where the process observes from or acts through.
|
|
49
|
+
Ask whether the user already has artifacts for the process, such as CSV or
|
|
50
|
+
Excel files, JSON samples, API docs, schemas, forms, screenshots, PDFs,
|
|
51
|
+
exports, message examples, or other content people already use. A small real
|
|
52
|
+
sample is often better than a long explanation. Remind the user to redact
|
|
53
|
+
secrets before attaching files or pasting content.
|
|
54
|
+
When relevant, look to the reusable `airlock-specs` library for starting
|
|
55
|
+
points, patterns, and ideas. Do not treat those library specs as guaranteed
|
|
56
|
+
descriptions of any third-party system. Prefer current API docs, real data
|
|
57
|
+
exports, samples, and user-provided artifacts when they conflict with the
|
|
58
|
+
library, and record the reason for the divergence.
|
|
59
|
+
Ask for the messy version. Help turn it into a small first Airlock spec and
|
|
60
|
+
a plan for more. Do not create the first workspace until the user chooses a
|
|
61
|
+
path.
|
|
38
62
|
4. Create `posts` only when the user chooses a feedback loop, asks for humans
|
|
39
63
|
and agents to provide feedback, or explicitly requests the posts pattern.
|
|
40
|
-
If the user is unsure,
|
|
64
|
+
If the user is unsure, continue process discovery before creating files.
|
|
41
65
|
5. Use `airlock-mcp import-spec <json-file> <name>` when starting from a
|
|
42
66
|
spec-library file, an exported `SPEC_CONFIG`, or an existing canonical config.
|
|
67
|
+
If the source is from `airlock-specs`, treat it as a reusable draft pattern.
|
|
68
|
+
Check actual API docs, CSV/Excel/JSON samples, schemas, or other artifacts
|
|
69
|
+
before assuming the source fields match a live third-party system.
|
|
43
70
|
6. Use `airlock-mcp clone <source-workspace> <name>` when creating a related
|
|
44
71
|
draft from an existing workspace. Treat clone like the old Streamlit UI:
|
|
45
72
|
preserve the shape but deliberately reset spec identity.
|
|
46
73
|
7. Keep the draft small. Prefer one useful governed output over a large
|
|
47
|
-
speculative system.
|
|
74
|
+
speculative system, then keep a plan for later specs.
|
|
48
75
|
8. On later sessions, run `airlock-mcp list-workspaces` when the target draft
|
|
49
76
|
is not obvious.
|
|
50
77
|
9. Run `airlock-mcp summary <workspace>` and
|
|
51
78
|
`airlock-mcp next <workspace>` before editing so
|
|
52
79
|
the current shape, sample count, access model, and local check status are
|
|
53
80
|
visible.
|
|
81
|
+
Present the summary back to the user as a structured spec card when useful:
|
|
82
|
+
core, file rules, attachments, guest access, column rules, samples, notes,
|
|
83
|
+
and check status. Do not make the user infer the current spec from file
|
|
84
|
+
links alone.
|
|
54
85
|
10. Run `airlock-mcp check <workspace>` after changing draft config or sample
|
|
55
86
|
records.
|
|
56
87
|
11. Keep `sample.records.json` as the agent-friendly authoring shape. Use
|
|
@@ -63,7 +94,9 @@ the deterministic checker.
|
|
|
63
94
|
|
|
64
95
|
## Spec Design Questions
|
|
65
96
|
|
|
66
|
-
|
|
97
|
+
Use these as an internal checklist. Do not dump the whole list into chat.
|
|
98
|
+
Write resolved answers into `decisions.md` and ask the human only for missing
|
|
99
|
+
or risky decisions:
|
|
67
100
|
|
|
68
101
|
1. What is one row about?
|
|
69
102
|
2. What systems, users, files, events, or reference data must be observed before
|
|
@@ -79,17 +112,52 @@ Resolve these in order, and write the answers into `decisions.md`:
|
|
|
79
112
|
10. Which optional context can live in a validated `variant` field?
|
|
80
113
|
11. Who submits, reviews, reads, delegates, and owns the process?
|
|
81
114
|
|
|
115
|
+
## Interfaces
|
|
116
|
+
|
|
117
|
+
Use interface to mean any place the process observes from or acts through.
|
|
118
|
+
Do not lead with the word before examples. Interfaces can be apps, files, forms,
|
|
119
|
+
people, emails, calls, mail, websites, APIs, data feeds, physical events,
|
|
120
|
+
shared folders, payment tools, bank apps, or other systems.
|
|
121
|
+
|
|
122
|
+
When a user names a process, map the interfaces both ways:
|
|
123
|
+
|
|
124
|
+
- Observe: where information enters the loop.
|
|
125
|
+
- Act: where a decision writes back to the world.
|
|
126
|
+
|
|
127
|
+
Spot gaps gently. A user may have one dataset but need additional observations
|
|
128
|
+
to orient well, or may rely on hard-to-automate interfaces such as phone calls
|
|
129
|
+
or physical mail.
|
|
130
|
+
|
|
131
|
+
## Artifacts And Libraries
|
|
132
|
+
|
|
133
|
+
Start from artifacts whenever they exist. Useful design artifacts include CSV
|
|
134
|
+
or Excel files, JSON samples, API docs, schemas, forms, screenshots, PDFs,
|
|
135
|
+
exports, message examples, and other defined content that already carries the
|
|
136
|
+
process shape.
|
|
137
|
+
|
|
138
|
+
Use design artifacts to infer row grain, field names, durable identifiers,
|
|
139
|
+
timestamps, evidence, variants, attachment needs, and edge cases. Distinguish
|
|
140
|
+
design artifacts from Airlock attachments: design artifacts help draft the spec;
|
|
141
|
+
attachments are evidence files later submitted with governed records.
|
|
142
|
+
|
|
143
|
+
Use `airlock-specs` as a source of reusable starting points, patterns, and
|
|
144
|
+
ideas. Do not promise that Airlock spec-library shapes reflect current
|
|
145
|
+
third-party APIs, exports, or business objects. If a library shape looks
|
|
146
|
+
outdated, overfit, underfit, or contradicted by current artifacts, override it
|
|
147
|
+
with the best current evidence and explain the decision in `decisions.md` or
|
|
148
|
+
`review.md`.
|
|
149
|
+
|
|
82
150
|
## OODA Loop
|
|
83
151
|
|
|
84
152
|
Use the observe-orient-decide-act loop as the product frame:
|
|
85
153
|
|
|
86
|
-
- Observe:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
- Act:
|
|
92
|
-
|
|
154
|
+
- Observe: controlled interface ingestion and observation/reference specs.
|
|
155
|
+
- Orient: context, proposals, scoring, exception queues, summaries, and gaps in
|
|
156
|
+
the observation set.
|
|
157
|
+
- Decide: governed choices by people or agents, with identity, timestamp,
|
|
158
|
+
rationale, evidence, approval, and separation of duties.
|
|
159
|
+
- Act: controlled writes back to interfaces, commitments, follow-ups, outputs,
|
|
160
|
+
and the next observations created by those actions.
|
|
93
161
|
|
|
94
162
|
## Pattern Guidance
|
|
95
163
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "Airlock MCP"
|
|
3
|
-
short_description: "
|
|
4
|
-
default_prompt: "Use $airlock-mcp to
|
|
3
|
+
short_description: "Improve a process with Airlock specs and OODA loop planning."
|
|
4
|
+
default_prompt: "Use $airlock-mcp to ask what process I want to improve, gather any existing artifacts, identify the loop around it, draft a small first Airlock spec, and keep a plan for more specs."
|
package/README.md
CHANGED
|
@@ -5,18 +5,21 @@ Airlock.
|
|
|
5
5
|
|
|
6
6
|
It covers the full Airlock loop:
|
|
7
7
|
|
|
8
|
-
-
|
|
9
|
-
-
|
|
8
|
+
- design specs with the bundled spec-building workbench
|
|
9
|
+
- map the process a person wants to improve into observe, orient, decide, and act
|
|
10
|
+
- use specs for governed data movement, decisions, actions, and feedback loops
|
|
10
11
|
- validate, create, and revise specs against installed Airlock
|
|
11
|
-
- capture real use cases and improvements so specs get better over time
|
|
12
12
|
|
|
13
13
|
Spec building is not a second thing users install. It is bundled inside
|
|
14
14
|
Airlock MCP.
|
|
15
15
|
|
|
16
|
-
Airlock
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
Airlock MCP gives agents two kinds of Airlock expertise:
|
|
17
|
+
|
|
18
|
+
1. Spec design: draft, check, revise, import, clone, and prepare specs for
|
|
19
|
+
installed Airlock validation.
|
|
20
|
+
2. Airlock operating patterns: use specs to organize observations, orientation,
|
|
21
|
+
governed decisions, controlled actions, separation of duties, and feedback
|
|
22
|
+
loops.
|
|
20
23
|
|
|
21
24
|
## Install
|
|
22
25
|
|
|
@@ -26,7 +29,7 @@ Dogfood directly from GitHub:
|
|
|
26
29
|
npx -y github:reunionstudio/airlock-mcp install --package github:reunionstudio/airlock-mcp
|
|
27
30
|
```
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
Install from npm:
|
|
30
33
|
|
|
31
34
|
```bash
|
|
32
35
|
npx @reunionstudio/airlock-mcp install
|
|
@@ -45,9 +48,14 @@ The GitHub dogfood command registers:
|
|
|
45
48
|
codex mcp add airlock -- npx -y github:reunionstudio/airlock-mcp server
|
|
46
49
|
```
|
|
47
50
|
|
|
48
|
-
The server exposes bootstrap guidance for starting a specs repo
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
The server exposes bootstrap guidance for starting a specs repo, coaching a
|
|
52
|
+
person through process discovery, and entering the bundled spec-building
|
|
53
|
+
workbench when a first spec is ready to draft.
|
|
54
|
+
|
|
55
|
+
Workspace summaries are structured spec cards. They present the current spec
|
|
56
|
+
core, file rules, attachment policy, guest access, column rules, sample record
|
|
57
|
+
shape, note-file status, and local check status so Codex can reflect the draft
|
|
58
|
+
back to the user before asking for decisions.
|
|
51
59
|
|
|
52
60
|
This install shape uses Node because `npx` runs npm package binaries. MCP itself
|
|
53
61
|
does not require Node. Once Airlock MCP does real operational work, such as
|
|
@@ -81,7 +89,7 @@ The server exposes orientation plus local spec-building tools:
|
|
|
81
89
|
|
|
82
90
|
- `airlock_start`: return setup guidance for a project.
|
|
83
91
|
- `airlock_doctor`: verify bundled workbench assets.
|
|
84
|
-
- `airlock_init_repo`: bootstrap a
|
|
92
|
+
- `airlock_init_repo`: bootstrap a Git-backed specs repo.
|
|
85
93
|
- `airlock_list_patterns` and `airlock_show_pattern`: inspect starter patterns.
|
|
86
94
|
- `airlock_init_workspace`: create a workspace from `blank` or `posts`.
|
|
87
95
|
- `airlock_list_workspaces`: inspect active or archived drafts.
|
|
@@ -114,13 +122,27 @@ The intended user flow is:
|
|
|
114
122
|
|
|
115
123
|
1. Run `npx @reunionstudio/airlock-mcp install` once for the agent environment.
|
|
116
124
|
2. Open Codex.
|
|
117
|
-
3. Create a
|
|
118
|
-
`home-specs`.
|
|
119
|
-
4.
|
|
120
|
-
|
|
121
|
-
5.
|
|
122
|
-
|
|
125
|
+
3. Create or open a Git-backed specs repo named for the org or project, such as
|
|
126
|
+
`home-specs`. GitHub is the recommended default when available.
|
|
127
|
+
4. If Codex is creating the repo, choose where the `home-specs` directory should
|
|
128
|
+
live before files are written.
|
|
129
|
+
5. Ask Codex to use Airlock MCP to help improve a process with Airlock specs.
|
|
130
|
+
6. Let Airlock MCP bootstrap the project, ask what process the user wants to
|
|
131
|
+
improve, and propose a small first spec plus a plan for more.
|
|
123
132
|
|
|
124
133
|
The first workspace should not be created automatically. The spec-building
|
|
125
|
-
workbench should
|
|
126
|
-
|
|
134
|
+
workbench should first ask for the messy process, identify where information
|
|
135
|
+
comes in and actions go out, then choose a small observation, orient, decision,
|
|
136
|
+
or action spec.
|
|
137
|
+
|
|
138
|
+
When the user already has artifacts, Airlock MCP should ask for them early:
|
|
139
|
+
CSV or Excel files, JSON samples, API docs, schemas, forms, screenshots, PDFs,
|
|
140
|
+
exports, message examples, or other defined content people already use. These
|
|
141
|
+
are design artifacts for drafting the spec; later Airlock attachments are
|
|
142
|
+
evidence files submitted with governed records.
|
|
143
|
+
|
|
144
|
+
Airlock MCP can also consult the reusable `airlock-specs` library for starting
|
|
145
|
+
points, patterns, and ideas. Those library specs are not guaranteed to reflect
|
|
146
|
+
the current shape of any third-party system. Current API docs, real exports,
|
|
147
|
+
samples, schemas, and user-provided artifacts should override library shapes
|
|
148
|
+
when they conflict.
|
package/docs/architecture.md
CHANGED
|
@@ -28,9 +28,10 @@ heavier operational tools mature.
|
|
|
28
28
|
|
|
29
29
|
The spec-building workbench lives in this repo under `src/airlock_mcp`,
|
|
30
30
|
`patterns`, `workspaces`, `schemas`, and `.agents/skills/airlock-mcp`.
|
|
31
|
-
Airlock
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
Airlock operating patterns live in the same MCP experience: real use cases,
|
|
32
|
+
controlled interface ingestion, OODA loops, governed decisions, separation of
|
|
33
|
+
duties, controlled actions, output review, and improvement capture. This is not
|
|
34
|
+
a second install or separate product surface.
|
|
34
35
|
|
|
35
36
|
## MCP Surface
|
|
36
37
|
|
|
@@ -44,10 +45,10 @@ The bootstrap server exposes:
|
|
|
44
45
|
`airlock_list_workspaces`, `airlock_check_workspace`, `airlock_summary`,
|
|
45
46
|
`airlock_next`, `airlock_export_csv`, and `airlock_render_sql`
|
|
46
47
|
|
|
47
|
-
These give the agent enough context to start a
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
the user chooses a path.
|
|
48
|
+
These give the agent enough context to start a Git-backed `<project>-specs`
|
|
49
|
+
repo in a user-chosen location, ask what process the user wants to improve,
|
|
50
|
+
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.
|
|
51
52
|
|
|
52
53
|
Workbench tools call the bundled Python package with argument arrays and a
|
|
53
54
|
controlled `PYTHONPATH`. They do not expose update commands over MCP. Tool
|
package/docs/install-surface.md
CHANGED
|
@@ -12,9 +12,12 @@ npx @reunionstudio/airlock-mcp install
|
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
Airlock MCP is the single installed interface for agents working with Airlock.
|
|
15
|
-
Then the user opens Codex, creates a
|
|
16
|
-
chatting with Airlock.
|
|
17
|
-
|
|
15
|
+
Then the user opens Codex, creates or opens a Git-backed `<project>-specs`
|
|
16
|
+
repo, and starts chatting with Airlock. GitHub is the recommended default when
|
|
17
|
+
available. If Codex is creating the repo, it should ask where the directory
|
|
18
|
+
should live before writing files. Airlock MCP should start by asking what
|
|
19
|
+
process the user wants to improve, then help design specs and operating
|
|
20
|
+
patterns around observe, orient, decide, and act.
|
|
18
21
|
|
|
19
22
|
The install command is not the spec workspace. It is the connector/setup entry
|
|
20
23
|
point. The specs repo is still the durable memory.
|
|
@@ -106,7 +109,13 @@ packaging pressure appears.
|
|
|
106
109
|
The first release should optimize the user journey:
|
|
107
110
|
|
|
108
111
|
1. install connector
|
|
109
|
-
2. create
|
|
110
|
-
3.
|
|
111
|
-
4.
|
|
112
|
-
5.
|
|
112
|
+
2. create or open a Git-backed `<project>-specs` Codex project
|
|
113
|
+
3. ask where the repo directory should live if Codex is creating it
|
|
114
|
+
4. ask what process the user wants to improve
|
|
115
|
+
5. ask for existing artifacts such as CSV, Excel, JSON, API docs, schemas,
|
|
116
|
+
forms, screenshots, PDFs, exports, or message examples
|
|
117
|
+
6. use `airlock-specs` library patterns as starting points when useful, while
|
|
118
|
+
preferring current artifacts over library shapes when they conflict
|
|
119
|
+
7. map where information comes in and actions go out
|
|
120
|
+
8. create the first workspace only after the user chooses a small first spec
|
|
121
|
+
9. keep a plan for later specs that improve the full loop
|
package/docs/ooda-loop.md
CHANGED
|
@@ -1,21 +1,38 @@
|
|
|
1
1
|
# OODA Loop For Airlock Specs
|
|
2
2
|
|
|
3
|
-
Airlock specs should support a
|
|
3
|
+
Airlock specs should support a process loop, not just a table.
|
|
4
|
+
|
|
5
|
+
Airlock works best when we can identify where information comes in and where
|
|
6
|
+
actions go out. Those places might be apps, files, forms, people, emails, calls,
|
|
7
|
+
mail, websites, APIs, data feeds, physical events, payment tools, bank apps, or
|
|
8
|
+
shared folders. After those examples are clear, call them interfaces: where the
|
|
9
|
+
process observes from or acts through.
|
|
10
|
+
|
|
11
|
+
Start from existing artifacts when they exist: CSV or Excel files, JSON samples,
|
|
12
|
+
API docs, schemas, forms, screenshots, PDFs, exports, message examples, or other
|
|
13
|
+
defined content people already use. A real sample often reveals identifiers,
|
|
14
|
+
timestamps, optional fields, and edge cases faster than chat can.
|
|
15
|
+
|
|
16
|
+
The reusable `airlock-specs` library can provide starting points, patterns, and
|
|
17
|
+
ideas. It is not a guarantee of the current shape of any third-party API,
|
|
18
|
+
export, or business object. Current docs and actual samples should override the
|
|
19
|
+
library when they conflict.
|
|
4
20
|
|
|
5
21
|
## Observe
|
|
6
22
|
|
|
7
|
-
Name the evidence and
|
|
23
|
+
Name the interfaces, evidence, and signals that matter:
|
|
8
24
|
|
|
9
25
|
- human entries
|
|
10
26
|
- agent observations
|
|
11
27
|
- screenshots, PDFs, receipts, exports, or source files
|
|
28
|
+
- CSV, Excel, JSON, API docs, schemas, forms, or message examples
|
|
12
29
|
- source APIs or app objects
|
|
13
30
|
- existing Airlock specs or reference data
|
|
14
31
|
- downstream system state
|
|
15
32
|
|
|
16
33
|
## Orient
|
|
17
34
|
|
|
18
|
-
Turn observations into a
|
|
35
|
+
Turn observations into context that can support a decision:
|
|
19
36
|
|
|
20
37
|
- one row grain
|
|
21
38
|
- durable identifiers
|
|
@@ -27,14 +44,20 @@ Turn observations into a governed shape:
|
|
|
27
44
|
- workflow
|
|
28
45
|
- references
|
|
29
46
|
- expectations
|
|
47
|
+
- missing observations or quality gaps
|
|
30
48
|
|
|
31
49
|
## Decide
|
|
32
50
|
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
Capture the governed choice. A decision spec should identify who or what made
|
|
52
|
+
the choice, when it happened, which option was selected, what evidence or
|
|
53
|
+
rationale mattered, and what separation of duties applies.
|
|
35
54
|
|
|
36
55
|
## Act
|
|
37
56
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
57
|
+
Model the action that follows the decision: send an email, submit payment,
|
|
58
|
+
update a website, change a price, open a ticket, trigger outreach, produce a
|
|
59
|
+
filing, request more evidence, or create another governed output. The action
|
|
60
|
+
creates new observations and the loop spins again.
|
|
61
|
+
|
|
62
|
+
Start with one small useful spec, then keep a plan for the next specs that
|
|
63
|
+
complete or improve the loop.
|
|
@@ -125,8 +125,9 @@ patterns, bootstrap, workspace files, local checks, and the Codex skill.
|
|
|
125
125
|
|
|
126
126
|
That command is the agent connector/setup entry point, not the user's spec
|
|
127
127
|
workspace. After installing or registering the connector, the user still creates
|
|
128
|
-
a separate `<project>-specs` repo
|
|
129
|
-
|
|
128
|
+
or opens a separate Git-backed `<project>-specs` repo in a user-chosen location
|
|
129
|
+
and works there with Codex. GitHub is the recommended default when available.
|
|
130
|
+
Users should ask for Airlock, not install a separate spec-building tool.
|
|
130
131
|
|
|
131
132
|
For Codex, the install command should use the platform MCP manager rather than
|
|
132
133
|
editing config files directly:
|
package/docs/workflows.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Start A New Specs Repo
|
|
4
4
|
|
|
5
|
-
Use this when a person opens a
|
|
5
|
+
Use this when a person opens a new Codex project and wants to start working
|
|
6
6
|
with real Airlock specs. The project repo is where they work. Public users
|
|
7
7
|
should come through the single Airlock MCP install; this repo provides the
|
|
8
8
|
spec-building capability behind that experience.
|
|
@@ -10,6 +10,11 @@ spec-building capability behind that experience.
|
|
|
10
10
|
Ask for the project or organization name first, then lowercase it, replace
|
|
11
11
|
spaces with hyphens, and append `-specs`. For example, `Home` becomes
|
|
12
12
|
`home-specs`. Reserve `airlock-specs` for the canonical reusable spec library.
|
|
13
|
+
Recommend a real Git repo for the specs project. GitHub is the recommended
|
|
14
|
+
default when the user already has GitHub set up, but any normal repository host
|
|
15
|
+
is acceptable. If Codex is creating the repo, ask where the `<slug>-specs`
|
|
16
|
+
directory should live before making files, then offer to initialize git and
|
|
17
|
+
create or push the GitHub repo.
|
|
13
18
|
Airlock itself does not create this repo; Codex can create it locally while
|
|
14
19
|
helping the user start an Airlock specs project.
|
|
15
20
|
Start in Codex, not Snowflake Cortex. Snow CLI or Cortex only matters later when
|
|
@@ -20,10 +25,22 @@ The preferred Codex prompt is:
|
|
|
20
25
|
```text
|
|
21
26
|
I want to use Airlock MCP to start working with Airlock specs for Home.
|
|
22
27
|
|
|
23
|
-
Set up this project as an Airlock specs repo.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
Set up this project as an Airlock specs repo. If this project is not already a
|
|
29
|
+
Git repo, recommend storing it in version control, preferably GitHub if
|
|
30
|
+
available. If you are creating the repo for me, ask where the `home-specs`
|
|
31
|
+
directory should live before making files.
|
|
32
|
+
|
|
33
|
+
Welcome me by asking what process I want to improve. Explain that Airlock works
|
|
34
|
+
best when we can identify the loop around that process: what information comes
|
|
35
|
+
in, what context helps us understand it, what decision needs to be made, and
|
|
36
|
+
what action happens after the decision. Ask whether I already have artifacts:
|
|
37
|
+
CSV or Excel files, JSON samples, API docs, schemas, forms, screenshots, PDFs,
|
|
38
|
+
exports, message examples, or other content people already use. When useful,
|
|
39
|
+
check the reusable airlock-specs library for starting points, patterns, and
|
|
40
|
+
ideas, but prefer current API docs, real exports, samples, and other artifacts
|
|
41
|
+
when they conflict with a library shape. Ask for the messy version, then help
|
|
42
|
+
turn it into a small first Airlock spec and a plan for more. Do not create the
|
|
43
|
+
first workspace until I choose a path.
|
|
27
44
|
```
|
|
28
45
|
|
|
29
46
|
When dogfooding this implementation repo before the MCP package is published,
|
|
@@ -50,9 +67,10 @@ The connector implementation lives in `reunionstudio/airlock-mcp`; this
|
|
|
50
67
|
workbench keeps the Airlock MCP spec-building capability.
|
|
51
68
|
|
|
52
69
|
That should install or register the Airlock MCP connector for the user's agent
|
|
53
|
-
environment. After that, the user still creates
|
|
54
|
-
and starts chatting there. Airlock MCP covers building
|
|
55
|
-
pull and push governed data, and improving specs from real
|
|
70
|
+
environment. After that, the user still creates or opens a Git-backed
|
|
71
|
+
`<slug>-specs` project and starts chatting there. Airlock MCP covers building
|
|
72
|
+
specs, using specs to pull and push governed data, and improving specs from real
|
|
73
|
+
use cases.
|
|
56
74
|
|
|
57
75
|
For Codex, the install command should register the server with:
|
|
58
76
|
|
|
@@ -64,28 +82,59 @@ The MCP-style flow should be:
|
|
|
64
82
|
|
|
65
83
|
1. Run `npx @reunionstudio/airlock-mcp install` once for the agent environment.
|
|
66
84
|
2. Open Codex.
|
|
67
|
-
3. Create a
|
|
85
|
+
3. Create or open a Git-backed specs repo named for the org or project, such as
|
|
68
86
|
`home-specs`.
|
|
69
|
-
4.
|
|
70
|
-
|
|
71
|
-
|
|
87
|
+
4. If Codex is creating it, choose where the repo directory should live before
|
|
88
|
+
files are written.
|
|
89
|
+
5. Tell Codex: `Use Airlock to help me improve a process with specs.`
|
|
90
|
+
6. Airlock MCP welcomes, asks what process the user wants to improve, explains
|
|
91
|
+
the loop around the process, and proposes a small first spec plus a plan for
|
|
92
|
+
more.
|
|
72
93
|
|
|
73
94
|
`init-repo` creates `AGENTS.md`, `.agents/skills/airlock-mcp/SKILL.md`, and
|
|
74
95
|
`workspaces/`. That is what lets Codex understand prompts such as
|
|
75
96
|
`Use $airlock-mcp` inside the specs repo.
|
|
76
97
|
|
|
77
|
-
After bootstrap,
|
|
98
|
+
After bootstrap, do not start with a pattern picker. Ask:
|
|
78
99
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
100
|
+
```text
|
|
101
|
+
What process do you want to improve?
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Then explain:
|
|
105
|
+
|
|
106
|
+
- Airlock works best when we can identify what information comes in.
|
|
107
|
+
- Airlock works best when we can identify what context helps people or agents
|
|
108
|
+
understand that information.
|
|
109
|
+
- Airlock works best when we can identify what decision needs to be made.
|
|
110
|
+
- Airlock works best when we can identify what action happens after the
|
|
111
|
+
decision.
|
|
112
|
+
|
|
113
|
+
Information may come from apps, files, forms, people, emails, calls, mail,
|
|
114
|
+
websites, APIs, data feeds, or physical events. Actions may go back through
|
|
115
|
+
those same places. After giving examples, call these places interfaces: where
|
|
116
|
+
the process observes from or acts through.
|
|
117
|
+
|
|
118
|
+
Ask whether the user already has artifacts for the process: CSV or Excel files,
|
|
119
|
+
JSON samples, API docs, schemas, forms, screenshots, PDFs, exports, message
|
|
120
|
+
examples, or other content people already use. Treat these as design artifacts
|
|
121
|
+
for drafting the spec. They are different from Airlock attachments, which are
|
|
122
|
+
evidence files submitted later with governed records.
|
|
123
|
+
|
|
124
|
+
When useful, point at the reusable `airlock-specs` library for starting points,
|
|
125
|
+
patterns, and ideas. Do not treat those library specs as guaranteed to reflect
|
|
126
|
+
the current shape of third-party systems. If current API docs, exports, samples,
|
|
127
|
+
schemas, or user-provided artifacts disagree with the library, prefer the
|
|
128
|
+
current artifacts and record the divergence.
|
|
129
|
+
|
|
130
|
+
Ask for the messy version. Help turn it into a small first Airlock spec and a
|
|
131
|
+
plan for more.
|
|
83
132
|
|
|
84
133
|
## Start From Feedback
|
|
85
134
|
|
|
86
135
|
Use this when the user chooses a shared feedback loop for humans and agents.
|
|
87
|
-
If they are not sure what to model yet,
|
|
88
|
-
this workspace.
|
|
136
|
+
If they are not sure what to model yet, continue process discovery before
|
|
137
|
+
creating this workspace.
|
|
89
138
|
|
|
90
139
|
```bash
|
|
91
140
|
airlock-mcp init feedback-loop --pattern posts
|
|
@@ -124,6 +173,11 @@ Supported input shapes:
|
|
|
124
173
|
- a raw canonical object with `core_config` or `column_config`
|
|
125
174
|
- a legacy `{"specs": [{"config": ...}]}`
|
|
126
175
|
|
|
176
|
+
Spec-library imports are starting points, not live system contracts. Before
|
|
177
|
+
using one for a third-party system, compare it with current API docs, CSV or
|
|
178
|
+
Excel exports, JSON samples, schemas, forms, and other real artifacts. Override
|
|
179
|
+
outdated or mismatched fields when current evidence says the shape has changed.
|
|
180
|
+
|
|
127
181
|
## Clone A Workspace
|
|
128
182
|
|
|
129
183
|
Use this when a related spec should preserve most design choices but needs a new
|
|
@@ -167,7 +221,10 @@ airlock-mcp next workspaces/team-posts
|
|
|
167
221
|
|
|
168
222
|
The summary shows the current spec identity, required fields, variant fields,
|
|
169
223
|
attachment posture, guest access model, sample record count, note-file status,
|
|
170
|
-
and local check counts.
|
|
224
|
+
and local check counts. It is organized as a structured spec card with sections
|
|
225
|
+
for core, file rules, attachments, guest access, column rules, samples, notes,
|
|
226
|
+
and check status so Codex can present the draft back to the user before asking
|
|
227
|
+
for decisions.
|
|
171
228
|
|
|
172
229
|
`next` prints the same recap plus a conservative next action: fix local errors,
|
|
173
230
|
review warnings, fill open design prompts in `decisions.md`, add sample records,
|
package/package.json
CHANGED
package/setup.py
CHANGED
|
@@ -29,6 +29,13 @@ Reserve `airlock-specs` for the canonical reusable Airlock spec library. A
|
|
|
29
29
|
team, customer, domain, or project should use its own scoped repo name such as
|
|
30
30
|
`home-specs`, `acme-finance-specs`, or `customer-onboarding-specs`.
|
|
31
31
|
|
|
32
|
+
Specs work should live in a real version-controlled repo. Prefer GitHub when
|
|
33
|
+
the user already uses GitHub, but any normal Git host is acceptable. Before
|
|
34
|
+
creating a new `<slug>-specs` directory, ask where it should live. If Codex can
|
|
35
|
+
create the repo, offer to initialize git and create or push the GitHub repo. If
|
|
36
|
+
not, ask the user to create the repo and open it in Codex before bootstrapping
|
|
37
|
+
Airlock MCP files.
|
|
38
|
+
|
|
32
39
|
Airlock itself does not create this repo. Codex can create it locally while
|
|
33
40
|
helping the user start an Airlock MCP project, then Airlock receives the
|
|
34
41
|
finished spec later. Start in Codex, not Snowflake Cortex. Snow CLI or Cortex
|
|
@@ -37,23 +44,47 @@ installed Airlock app.
|
|
|
37
44
|
|
|
38
45
|
## Starting A Spec Project
|
|
39
46
|
|
|
40
|
-
After bootstrap, welcome the user and orient before creating a workspace.
|
|
41
|
-
|
|
47
|
+
After bootstrap, welcome the user and orient before creating a workspace. Start
|
|
48
|
+
with:
|
|
49
|
+
|
|
50
|
+
What process do you want to improve?
|
|
51
|
+
|
|
52
|
+
Explain that Airlock works best when we can identify the loop around that
|
|
53
|
+
process:
|
|
54
|
+
|
|
55
|
+
1. what information comes in
|
|
56
|
+
2. what context helps people or agents understand it
|
|
57
|
+
3. what decision needs to be made
|
|
58
|
+
4. what action happens after the decision
|
|
59
|
+
|
|
60
|
+
Information may come from apps, files, forms, people, emails, calls, mail,
|
|
61
|
+
websites, APIs, data feeds, or physical events. Actions may go back through
|
|
62
|
+
those same places. After giving examples, call these places interfaces: where
|
|
63
|
+
the process observes from or acts through.
|
|
64
|
+
|
|
65
|
+
Ask whether the user already has artifacts for the process: CSV or Excel files,
|
|
66
|
+
JSON samples, API docs, schemas, forms, screenshots, PDFs, exports, message
|
|
67
|
+
examples, or other defined content people already use. A small real sample is
|
|
68
|
+
often better than a long explanation. Remind the user to redact secrets before
|
|
69
|
+
attaching files or pasting content.
|
|
42
70
|
|
|
43
|
-
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
71
|
+
Use `airlock-specs` as a reusable library of starting points, patterns, and
|
|
72
|
+
ideas when it is available. Do not assume library specs match the current shape
|
|
73
|
+
of a third-party API, export, or business object. Prefer current API docs, real
|
|
74
|
+
data exports, samples, and user-provided artifacts when they conflict with the
|
|
75
|
+
library, and record the reason for the divergence.
|
|
47
76
|
|
|
48
|
-
|
|
49
|
-
for the
|
|
77
|
+
Ask for the messy version. Help turn it into a small first Airlock spec and a
|
|
78
|
+
plan for more. Do not create the first workspace until the user chooses a path.
|
|
79
|
+
Create `posts` only when the user wants a shared feedback loop or explicitly
|
|
80
|
+
asks for the posts pattern.
|
|
50
81
|
|
|
51
82
|
## Working Style
|
|
52
83
|
|
|
53
84
|
- Use the repo-scoped `$airlock-mcp` skill for spec drafting, review, and
|
|
54
85
|
pattern selection.
|
|
55
86
|
- Keep drafts small and concrete. Prefer one useful governed output over a
|
|
56
|
-
large speculative process map.
|
|
87
|
+
large speculative process map, then keep a plan for later specs.
|
|
57
88
|
- Preserve decisions in workspace files so future Codex sessions can resume
|
|
58
89
|
without relying on chat memory.
|
|
59
90
|
- Use `airlock-mcp list-workspaces` before guessing which draft to resume.
|
|
@@ -78,6 +109,11 @@ Resolve these before final JSON:
|
|
|
78
109
|
- workflow and pushback
|
|
79
110
|
- references and expectations
|
|
80
111
|
- delegation and agent identity
|
|
112
|
+
- interfaces observed from or acted through
|
|
113
|
+
- existing artifacts such as CSV, Excel, JSON, API docs, schemas, exports,
|
|
114
|
+
forms, screenshots, PDFs, or message examples
|
|
115
|
+
- whether an `airlock-specs` library pattern was used, changed, or rejected
|
|
116
|
+
- likely gaps in observations, orientation, decisions, or actions
|
|
81
117
|
- observe-orient-decide-act loop
|
|
82
118
|
|
|
83
119
|
Do not encode Airlock lifecycle state, reviewer notes, approval status, or
|
|
@@ -154,8 +190,9 @@ def format_bootstrap_result(result: BootstrapResult) -> str:
|
|
|
154
190
|
"",
|
|
155
191
|
"next:",
|
|
156
192
|
"1. Open this repo in Codex.",
|
|
157
|
-
"2.
|
|
158
|
-
"3.
|
|
193
|
+
"2. Keep this specs project in git; GitHub is recommended when available.",
|
|
194
|
+
"3. Ask: Use Airlock to help me build and use specs.",
|
|
195
|
+
"4. Start with: What process do you want to improve?",
|
|
159
196
|
]
|
|
160
197
|
)
|
|
161
198
|
return "\n".join(lines)
|
package/src/airlock_mcp/cli.py
CHANGED
|
@@ -303,7 +303,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
303
303
|
about_parser = subparsers.add_parser("about", help="Show the Airlock MCP mark and command map.")
|
|
304
304
|
about_parser.set_defaults(func=about)
|
|
305
305
|
|
|
306
|
-
init_repo_parser = subparsers.add_parser("init-repo", help="Prepare a specs repo for Codex and Airlock MCP.")
|
|
306
|
+
init_repo_parser = subparsers.add_parser("init-repo", help="Prepare a Git-backed specs repo for Codex and Airlock MCP.")
|
|
307
307
|
init_repo_parser.add_argument("path", nargs="?", default=".", help="Specs repo path. Defaults to current directory.")
|
|
308
308
|
init_repo_parser.add_argument("--force", action="store_true", help="Overwrite AGENTS.md and the repo-scoped skill.")
|
|
309
309
|
init_repo_parser.set_defaults(func=init_repo)
|
|
@@ -233,8 +233,24 @@ def next_steps(workspace: Path) -> str:
|
|
|
233
233
|
lines.append(f"2. Run `airlock-mcp summary {workspace}` to confirm the shape.")
|
|
234
234
|
return "\n".join(lines)
|
|
235
235
|
if _text_has_open_placeholders(workspace / "decisions.md"):
|
|
236
|
-
|
|
237
|
-
|
|
236
|
+
spec_name = ""
|
|
237
|
+
if isinstance(spec, dict):
|
|
238
|
+
core_config = spec.get("core_config")
|
|
239
|
+
if isinstance(core_config, dict):
|
|
240
|
+
spec_name = str(core_config.get("spec_name") or "")
|
|
241
|
+
if spec_name == "posts":
|
|
242
|
+
lines.append("1. Record the starter assumptions in decisions.md, or change only what is wrong.")
|
|
243
|
+
lines.append(
|
|
244
|
+
"2. Defaults: one row is one post/request/observation/response; "
|
|
245
|
+
"submitters are the owner and approved agents; evidence is optional; "
|
|
246
|
+
"posted_at is authored or captured time."
|
|
247
|
+
)
|
|
248
|
+
lines.append("3. Use posts to collect messy process feedback, then plan the first observe, orient, decide, or act spec.")
|
|
249
|
+
else:
|
|
250
|
+
lines.append("1. Record the messy process goal and first-loop assumptions in decisions.md.")
|
|
251
|
+
lines.append("2. Identify what is observed, what helps orient, what decision is governed, and what action follows.")
|
|
252
|
+
lines.append("3. Keep the first spec small and note later specs in review.md.")
|
|
253
|
+
lines.append(f"4. Run `airlock-mcp next {workspace}` when the design notes are current.")
|
|
238
254
|
return "\n".join(lines)
|
|
239
255
|
records = sample.get("records") if isinstance(sample, dict) else []
|
|
240
256
|
if not records:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from typing import Any
|
|
5
6
|
|
|
@@ -30,6 +31,40 @@ def _variant_shape_fields(spec_config: dict[str, Any]) -> set[str]:
|
|
|
30
31
|
return fields
|
|
31
32
|
|
|
32
33
|
|
|
34
|
+
def _format_value(value: Any, *, default: str = "unknown") -> str:
|
|
35
|
+
if value is None:
|
|
36
|
+
return default
|
|
37
|
+
if isinstance(value, bool):
|
|
38
|
+
return "true" if value else "false"
|
|
39
|
+
if isinstance(value, (dict, list)):
|
|
40
|
+
text = json.dumps(value, sort_keys=True, separators=(",", ":"))
|
|
41
|
+
else:
|
|
42
|
+
text = str(value)
|
|
43
|
+
return text if text else default
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _format_literal(value: Any, *, default: str = "unknown") -> str:
|
|
47
|
+
if value is None:
|
|
48
|
+
return default
|
|
49
|
+
if isinstance(value, bool):
|
|
50
|
+
return "true" if value else "false"
|
|
51
|
+
if isinstance(value, str):
|
|
52
|
+
return json.dumps(value)
|
|
53
|
+
return str(value)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _join(values: list[str], *, default: str = "none") -> str:
|
|
57
|
+
cleaned = [value for value in values if value]
|
|
58
|
+
return ", ".join(cleaned) if cleaned else default
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _truncate(value: Any, limit: int = 96) -> str:
|
|
62
|
+
text = _format_value(value, default="")
|
|
63
|
+
if len(text) <= limit:
|
|
64
|
+
return text
|
|
65
|
+
return text[: limit - 3] + "..."
|
|
66
|
+
|
|
67
|
+
|
|
33
68
|
def _guest_access_summary(spec_config: dict[str, Any]) -> str:
|
|
34
69
|
guest_access = spec_config.get("guest_access")
|
|
35
70
|
if not isinstance(guest_access, dict):
|
|
@@ -50,6 +85,34 @@ def _guest_access_summary(spec_config: dict[str, Any]) -> str:
|
|
|
50
85
|
return "shared public folder: " + (", ".join(enabled) if enabled else "no enabled subfolders")
|
|
51
86
|
|
|
52
87
|
|
|
88
|
+
def _enabled_guest_subfolders(guest_access: dict[str, Any]) -> list[str]:
|
|
89
|
+
public_folder = guest_access.get("public_folder")
|
|
90
|
+
if not isinstance(public_folder, dict):
|
|
91
|
+
return []
|
|
92
|
+
subfolders = public_folder.get("subfolders")
|
|
93
|
+
if not isinstance(subfolders, dict):
|
|
94
|
+
return []
|
|
95
|
+
return [
|
|
96
|
+
str(name)
|
|
97
|
+
for name, value in subfolders.items()
|
|
98
|
+
if isinstance(value, dict) and value.get("enabled") is True
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _guest_role_lines(guest_access: dict[str, Any]) -> list[str]:
|
|
103
|
+
roles = guest_access.get("guest_roles")
|
|
104
|
+
if not isinstance(roles, list):
|
|
105
|
+
return []
|
|
106
|
+
lines: list[str] = []
|
|
107
|
+
for role in roles:
|
|
108
|
+
if not isinstance(role, dict):
|
|
109
|
+
continue
|
|
110
|
+
role_name = _format_value(role.get("role_name"))
|
|
111
|
+
access_level = _format_value(role.get("access_level"))
|
|
112
|
+
lines.append(f"{role_name} -> {access_level}")
|
|
113
|
+
return lines
|
|
114
|
+
|
|
115
|
+
|
|
53
116
|
def _attachment_summary(spec_config: dict[str, Any]) -> str:
|
|
54
117
|
policy = spec_config.get("attachment_policy")
|
|
55
118
|
if not isinstance(policy, dict):
|
|
@@ -59,6 +122,86 @@ def _attachment_summary(spec_config: dict[str, Any]) -> str:
|
|
|
59
122
|
return "required" if policy.get("attachment_required") is True else "optional"
|
|
60
123
|
|
|
61
124
|
|
|
125
|
+
def _column_tests(column: dict[str, Any]) -> list[str]:
|
|
126
|
+
tests = column.get("tests")
|
|
127
|
+
return [str(test) for test in tests if isinstance(test, str)] if isinstance(tests, list) else []
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _column_line(column: dict[str, Any], variant_shapes: set[str]) -> str:
|
|
131
|
+
name = _format_value(column.get("name"))
|
|
132
|
+
column_type = _format_value(column.get("type"))
|
|
133
|
+
tests = _column_tests(column)
|
|
134
|
+
attributes: list[str] = []
|
|
135
|
+
if "not_null" in tests:
|
|
136
|
+
attributes.append("required")
|
|
137
|
+
if "unique" in tests:
|
|
138
|
+
attributes.append("unique")
|
|
139
|
+
extra_tests = [test for test in tests if test not in {"not_null", "unique"}]
|
|
140
|
+
if extra_tests:
|
|
141
|
+
attributes.append("tests: " + ", ".join(extra_tests))
|
|
142
|
+
if column.get("type") == "variant":
|
|
143
|
+
attributes.append("variant shaped" if name in variant_shapes else "variant unshaped")
|
|
144
|
+
if column.get("format"):
|
|
145
|
+
attributes.append("format " + _format_value(column.get("format")))
|
|
146
|
+
description = column.get("description")
|
|
147
|
+
suffix = f" - {_truncate(description, 140)}" if isinstance(description, str) and description else ""
|
|
148
|
+
return f" - {name}: {column_type}; {_join(attributes)}{suffix}"
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _variant_rule_lines(spec_config: dict[str, Any]) -> list[str]:
|
|
152
|
+
rules = spec_config.get("rules")
|
|
153
|
+
if not isinstance(rules, list):
|
|
154
|
+
return []
|
|
155
|
+
lines: list[str] = []
|
|
156
|
+
for rule in rules:
|
|
157
|
+
if not isinstance(rule, dict) or rule.get("type") != "variant_shape":
|
|
158
|
+
continue
|
|
159
|
+
field = rule.get("field", rule.get("column"))
|
|
160
|
+
if not isinstance(field, str) or not field:
|
|
161
|
+
field = "unknown"
|
|
162
|
+
roots = rule.get("allowed_root_keys")
|
|
163
|
+
root_values = [str(value) for value in roots if isinstance(value, str)] if isinstance(roots, list) else []
|
|
164
|
+
paths = rule.get("paths")
|
|
165
|
+
path_count = len(paths) if isinstance(paths, list) else 0
|
|
166
|
+
required_count = (
|
|
167
|
+
len([path for path in paths if isinstance(path, dict) and path.get("required") is True])
|
|
168
|
+
if isinstance(paths, list)
|
|
169
|
+
else 0
|
|
170
|
+
)
|
|
171
|
+
lines.append(
|
|
172
|
+
f" - {field}: roots {_join(root_values)}; paths {path_count}; required_paths {required_count}"
|
|
173
|
+
)
|
|
174
|
+
return lines
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _ordered_record_fields(columns: list[dict[str, Any]], record: dict[str, Any]) -> list[str]:
|
|
178
|
+
ordered = [
|
|
179
|
+
str(column.get("name"))
|
|
180
|
+
for column in columns
|
|
181
|
+
if isinstance(column.get("name"), str) and column.get("name") in record
|
|
182
|
+
]
|
|
183
|
+
extras = sorted(str(key) for key in record if str(key) not in ordered)
|
|
184
|
+
return ordered + extras
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _sample_key(columns: list[dict[str, Any]], records: list[Any]) -> str:
|
|
188
|
+
if not records or not isinstance(records[0], dict):
|
|
189
|
+
return "none"
|
|
190
|
+
first = records[0]
|
|
191
|
+
preferred: list[str] = []
|
|
192
|
+
for test_name in ("unique", "not_null"):
|
|
193
|
+
preferred.extend(
|
|
194
|
+
str(column.get("name"))
|
|
195
|
+
for column in columns
|
|
196
|
+
if isinstance(column.get("name"), str) and test_name in _column_tests(column)
|
|
197
|
+
)
|
|
198
|
+
preferred.extend(str(column.get("name")) for column in columns if isinstance(column.get("name"), str))
|
|
199
|
+
for field in preferred:
|
|
200
|
+
if field in first and not isinstance(first[field], (dict, list)):
|
|
201
|
+
return f"{field}={_truncate(first[field])}"
|
|
202
|
+
return "none"
|
|
203
|
+
|
|
204
|
+
|
|
62
205
|
def _text_status(workspace: Path, filename: str) -> str:
|
|
63
206
|
path = workspace / filename
|
|
64
207
|
if not path.exists():
|
|
@@ -82,34 +225,83 @@ def workspace_summary(
|
|
|
82
225
|
variants = [str(column.get("name")) for column in columns if column.get("type") == "variant"]
|
|
83
226
|
variant_shapes = _variant_shape_fields(spec_config)
|
|
84
227
|
records = sample_records.get("records")
|
|
85
|
-
|
|
228
|
+
record_list = records if isinstance(records, list) else []
|
|
229
|
+
record_count = len(record_list)
|
|
230
|
+
first_record = record_list[0] if record_list and isinstance(record_list[0], dict) else {}
|
|
86
231
|
file_rules = spec_config.get("file_rules")
|
|
87
232
|
file_format = file_rules.get("file_format") if isinstance(file_rules, dict) else None
|
|
88
|
-
|
|
233
|
+
file_format = file_format if isinstance(file_format, dict) else {}
|
|
234
|
+
guest_access = spec_config.get("guest_access")
|
|
235
|
+
guest_access = guest_access if isinstance(guest_access, dict) else {}
|
|
236
|
+
attachment_policy = spec_config.get("attachment_policy")
|
|
237
|
+
attachment_policy = attachment_policy if isinstance(attachment_policy, dict) else {}
|
|
238
|
+
variant_rule_lines = _variant_rule_lines(spec_config)
|
|
89
239
|
|
|
90
240
|
lines = [
|
|
91
241
|
f"workspace: {workspace}",
|
|
92
242
|
f"spec: {core.get('spec_name', 'unknown')} ({core.get('spec_alias', 'no alias')})",
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
),
|
|
104
|
-
f"
|
|
105
|
-
f"
|
|
106
|
-
f"
|
|
107
|
-
f"
|
|
108
|
-
"
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
),
|
|
113
|
-
f"
|
|
243
|
+
"",
|
|
244
|
+
"core:",
|
|
245
|
+
f" spec_name: {_format_value(core.get('spec_name'))}",
|
|
246
|
+
f" spec_alias: {_format_value(core.get('spec_alias'), default='none')}",
|
|
247
|
+
f" owner_role: {_format_value(core.get('owner_role'))}",
|
|
248
|
+
f" published: {_format_value(core.get('is_published'))}",
|
|
249
|
+
f" archived: {_format_value(core.get('is_archived'))}",
|
|
250
|
+
f" description: {_truncate(core.get('description')) or 'none'}",
|
|
251
|
+
"",
|
|
252
|
+
"file_rules:",
|
|
253
|
+
f" file_type: {_format_value(file_format.get('file_type'))}",
|
|
254
|
+
f" parse_header: {_format_value(file_format.get('parse_header'))}",
|
|
255
|
+
f" save_header: {_format_value(file_format.get('save_header'))}",
|
|
256
|
+
f" field_delimiter: {_format_literal(file_format.get('field_delimiter'))}",
|
|
257
|
+
f" record_delimiter: {_format_literal(file_format.get('record_delimiter'))}",
|
|
258
|
+
f" encoding: {_format_value(file_format.get('encoding'))}",
|
|
259
|
+
"",
|
|
260
|
+
"attachments:",
|
|
261
|
+
f" summary: {_attachment_summary(spec_config)}",
|
|
262
|
+
f" enabled: {_format_value(attachment_policy.get('attachments_enabled'))}",
|
|
263
|
+
f" required: {_format_value(attachment_policy.get('attachment_required'))}",
|
|
264
|
+
"",
|
|
265
|
+
"guest_access:",
|
|
266
|
+
f" summary: {_guest_access_summary(spec_config)}",
|
|
267
|
+
f" isolated_directories_enabled: {_format_value(guest_access.get('isolated_directories_enabled'))}",
|
|
268
|
+
f" public_folder_enabled: {_format_value((guest_access.get('public_folder') or {}).get('enabled') if isinstance(guest_access.get('public_folder'), dict) else None)}",
|
|
269
|
+
f" enabled_subfolders: {_join(_enabled_guest_subfolders(guest_access))}",
|
|
270
|
+
f" guest_roles: {_join(_guest_role_lines(guest_access))}",
|
|
271
|
+
"",
|
|
272
|
+
"column_rules:",
|
|
273
|
+
f" total: {len(columns)}",
|
|
274
|
+
f" required_fields: {_join(required)}",
|
|
275
|
+
f" variant_fields: {_join([f'{name} shaped' if name in variant_shapes else f'{name} unshaped' for name in variants])}",
|
|
276
|
+
" columns:",
|
|
114
277
|
]
|
|
278
|
+
lines.extend(_column_line(column, variant_shapes) for column in columns)
|
|
279
|
+
lines.extend(
|
|
280
|
+
[
|
|
281
|
+
" variant_shape_rules:",
|
|
282
|
+
*(variant_rule_lines if variant_rule_lines else [" - none"]),
|
|
283
|
+
"",
|
|
284
|
+
"samples:",
|
|
285
|
+
f" spec_name: {_format_value(sample_records.get('spec_name'))}",
|
|
286
|
+
f" filename: {_format_value(sample_records.get('filename'))}",
|
|
287
|
+
f" records: {record_count}",
|
|
288
|
+
f" first_record_key: {_sample_key(columns, record_list)}",
|
|
289
|
+
f" first_record_fields: {_join(_ordered_record_fields(columns, first_record))}",
|
|
290
|
+
"",
|
|
291
|
+
"notes:",
|
|
292
|
+
*(
|
|
293
|
+
f" {_text_status(workspace, filename)}"
|
|
294
|
+
for filename in ("brief.md", "decisions.md", "questions.md", "review.md")
|
|
295
|
+
),
|
|
296
|
+
"",
|
|
297
|
+
"check:",
|
|
298
|
+
f" errors: {len(result.errors)}",
|
|
299
|
+
f" warnings: {len(result.warnings)}",
|
|
300
|
+
]
|
|
301
|
+
)
|
|
302
|
+
if result.findings:
|
|
303
|
+
lines.append(" findings:")
|
|
304
|
+
lines.extend(
|
|
305
|
+
f" - {finding.level}: {finding.path}: {finding.message}" for finding in result.findings
|
|
306
|
+
)
|
|
115
307
|
return "\n".join(lines)
|
package/src/mcp.mjs
CHANGED
|
@@ -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.2",
|
|
48
48
|
},
|
|
49
49
|
instructions:
|
|
50
|
-
"Airlock MCP helps agents
|
|
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.",
|
|
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
|
|
87
|
+
description: "Bootstrap a Git-backed specs repo and choose the first Airlock 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 and using Airlock specs in a
|
|
106
|
+
description: "Start building and using Airlock specs in a Git-backed specs repo.",
|
|
107
107
|
messages: [
|
|
108
108
|
{
|
|
109
109
|
role: "user",
|
package/src/text.mjs
CHANGED
|
@@ -21,18 +21,40 @@ export function airlockPrompt(project) {
|
|
|
21
21
|
const repoName = specsRepoName(project);
|
|
22
22
|
return `I want to use Airlock MCP to start working with Airlock specs for ${repoName}.
|
|
23
23
|
|
|
24
|
-
Set up this project as an Airlock specs repo.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
Set up this project as an Airlock specs repo. If this project is not already a
|
|
25
|
+
Git repo, recommend storing it in version control, preferably GitHub if that is
|
|
26
|
+
available. If you are creating the repo for me, ask where the ${repoName}
|
|
27
|
+
directory should live before making it, then offer to initialize git and create
|
|
28
|
+
or push a GitHub repo.
|
|
29
|
+
|
|
30
|
+
Welcome me by asking what process I want to improve. Explain that Airlock works
|
|
31
|
+
best when we can identify the loop around that process: what information comes
|
|
32
|
+
in, what context helps us understand it, what decision needs to be made, and
|
|
33
|
+
what action happens after the decision. Information may come from apps, files,
|
|
34
|
+
forms, people,
|
|
35
|
+
emails, calls, mail, websites, APIs, data feeds, or physical events. Actions
|
|
36
|
+
may go back through those same places. Ask whether I already have artifacts:
|
|
37
|
+
CSV or Excel files, JSON samples, API docs, schemas, forms, screenshots, PDFs,
|
|
38
|
+
exports, message examples, or other content people already use. Treat a small
|
|
39
|
+
real sample as stronger evidence than a long explanation, and remind me to
|
|
40
|
+
redact secrets.
|
|
41
|
+
|
|
42
|
+
When useful, check the reusable airlock-specs library for starting points,
|
|
43
|
+
patterns, and ideas, but do not assume those library specs match current
|
|
44
|
+
third-party systems. Prefer current API docs, real exports, samples, and other
|
|
45
|
+
artifacts when they conflict with a library shape.
|
|
46
|
+
|
|
47
|
+
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.`;
|
|
28
49
|
}
|
|
29
50
|
|
|
30
51
|
export function nextSteps(project) {
|
|
31
52
|
const repoName = specsRepoName(project);
|
|
32
53
|
return `Next:
|
|
33
54
|
1. Open Codex.
|
|
34
|
-
2. Create a
|
|
35
|
-
3.
|
|
55
|
+
2. Create or open a Git-backed specs repo named ${repoName}. GitHub is the recommended default when available.
|
|
56
|
+
3. If Codex is creating it, ask Codex where the ${repoName} directory should live before it makes files.
|
|
57
|
+
4. Ask Codex:
|
|
36
58
|
|
|
37
59
|
${airlockPrompt(project)
|
|
38
60
|
.split("\n")
|
|
@@ -40,47 +62,68 @@ ${airlockPrompt(project)
|
|
|
40
62
|
.join("\n")}
|
|
41
63
|
|
|
42
64
|
Airlock MCP will offer:
|
|
43
|
-
-
|
|
44
|
-
- spec
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
65
|
+
- process discovery before choosing a spec pattern
|
|
66
|
+
- spec design with the bundled workbench
|
|
67
|
+
- Airlock operating patterns for OODA loops and separation of duties
|
|
68
|
+
- observe specs for controlled interface ingestion
|
|
69
|
+
- orient specs for proposals, context, scoring, or exception queues
|
|
70
|
+
- decision specs and action specs for governed follow-through
|
|
71
|
+
- artifact-grounded drafts from CSV, Excel, JSON, API docs, schemas, forms, screenshots, PDFs, or exports
|
|
72
|
+
- airlock-specs library patterns as starting points, checked against current artifacts`;
|
|
49
73
|
}
|
|
50
74
|
|
|
51
75
|
export function gettingStartedText(project) {
|
|
52
76
|
return `# Airlock MCP
|
|
53
77
|
|
|
54
78
|
Airlock MCP is the single installed interface for AI agents working with
|
|
55
|
-
Airlock. It helps a person and their agent
|
|
56
|
-
|
|
79
|
+
Airlock. It helps a person and their agent improve processes by designing
|
|
80
|
+
specs, using specs for governed data movement, and planning OODA loops that
|
|
81
|
+
can be assisted by people or agents.
|
|
57
82
|
|
|
58
|
-
|
|
59
|
-
clones, and prepares specs for installed Airlock validation.
|
|
83
|
+
Airlock MCP gives agents two kinds of Airlock expertise:
|
|
60
84
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
Airlock
|
|
64
|
-
|
|
85
|
+
1. Spec design: draft, check, revise, import, clone, and prepare specs for
|
|
86
|
+
installed Airlock validation.
|
|
87
|
+
2. Airlock operating patterns: use specs to organize observations, orientation,
|
|
88
|
+
governed decisions, controlled actions, separation of duties, and feedback
|
|
89
|
+
loops.
|
|
65
90
|
|
|
66
|
-
Start in a
|
|
67
|
-
|
|
68
|
-
|
|
91
|
+
Start in a Git-backed specs repo such as ${specsRepoName(project)}. GitHub is
|
|
92
|
+
the recommended default when the user has it set up, but any normal repository
|
|
93
|
+
works. If Codex is creating the repo, ask where the directory should live before
|
|
94
|
+
making files. Do not work inside the Airlock MCP implementation repo unless you
|
|
95
|
+
are changing the tools themselves.
|
|
69
96
|
|
|
70
|
-
Use this prompt in the
|
|
97
|
+
Use this prompt in the specs repo:
|
|
71
98
|
|
|
72
99
|
${airlockPrompt(project)}
|
|
73
100
|
|
|
74
|
-
|
|
101
|
+
First ask: What process do you want to improve?
|
|
75
102
|
|
|
76
|
-
|
|
77
|
-
2. Start from a known process and create a blank workspace.
|
|
78
|
-
3. Create a posts feedback loop for humans and agents to submit requests,
|
|
79
|
-
observations, and responses.
|
|
80
|
-
4. Use Airlock Star with an installed Airlock app to validate specs, load data,
|
|
81
|
-
read outputs, plan push/pull workflows, and capture improvements.
|
|
103
|
+
Airlock works best when we can identify the loop around a process:
|
|
82
104
|
|
|
83
|
-
|
|
105
|
+
1. Observe: what information comes in.
|
|
106
|
+
2. Orient: what context helps people or agents understand it.
|
|
107
|
+
3. Decide: what choice needs to be governed.
|
|
108
|
+
4. Act: what happens after the decision.
|
|
109
|
+
|
|
110
|
+
Information may come from apps, files, forms, people, emails, calls, mail,
|
|
111
|
+
websites, APIs, data feeds, or physical events. Actions may go back through
|
|
112
|
+
those same places. Airlock calls these places interfaces: where the process
|
|
113
|
+
observes from or acts through.
|
|
114
|
+
|
|
115
|
+
If you already have artifacts, attach or point Codex at them: CSV or Excel
|
|
116
|
+
files, JSON samples, API docs, schemas, forms, screenshots, PDFs, exports,
|
|
117
|
+
message examples, or other defined content people already use. A small real
|
|
118
|
+
sample is often better than a long explanation. Redact secrets before sharing.
|
|
119
|
+
|
|
120
|
+
Airlock MCP can also use the reusable airlock-specs library for starting
|
|
121
|
+
points, patterns, and ideas. Library specs are not guaranteed to match the
|
|
122
|
+
current shape of any third-party system. Prefer current API docs, real exports,
|
|
123
|
+
samples, and other artifacts when they conflict with the library.
|
|
124
|
+
|
|
125
|
+
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.`;
|
|
84
127
|
}
|
|
85
128
|
|
|
86
129
|
export function helpText() {
|
|
@@ -103,6 +146,7 @@ Options:
|
|
|
103
146
|
|
|
104
147
|
Airlock MCP is the single installed interface for agents working with Airlock.
|
|
105
148
|
Spec building is bundled inside that experience.
|
|
106
|
-
Airlock
|
|
149
|
+
Airlock operating patterns help connect specs into OODA loops, separation of
|
|
150
|
+
duties, governed decisions, controlled actions, and feedback loops.
|
|
107
151
|
`;
|
|
108
152
|
}
|
package/src/workbench.mjs
CHANGED
|
@@ -31,7 +31,7 @@ export const WORKBENCH_TOOLS = [
|
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
name: "airlock_init_repo",
|
|
34
|
-
description: "Bootstrap a specs repo with AGENTS.md, the Airlock MCP skill, and workspaces/.",
|
|
34
|
+
description: "Bootstrap a Git-backed specs repo with AGENTS.md, the Airlock MCP skill, and workspaces/.",
|
|
35
35
|
inputSchema: objectSchema({
|
|
36
36
|
path: {
|
|
37
37
|
type: "string",
|