@reunionstudio/airlock-mcp 0.1.1 → 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/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 blank Codex project and wants to start working
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,13 +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. Welcome me by asking what
24
- process I want to improve. Explain that Airlock works best when we can
25
- identify the loop around that process: what information comes in, what context
26
- helps us understand it, what decision needs to be made, and what action happens
27
- after the decision. Ask for the messy version, then help turn it into a small
28
- first Airlock spec and a plan for more. Do not create the first workspace until
29
- I choose a path.
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.
30
44
  ```
31
45
 
32
46
  When dogfooding this implementation repo before the MCP package is published,
@@ -53,9 +67,10 @@ The connector implementation lives in `reunionstudio/airlock-mcp`; this
53
67
  workbench keeps the Airlock MCP spec-building capability.
54
68
 
55
69
  That should install or register the Airlock MCP connector for the user's agent
56
- environment. After that, the user still creates a blank `<slug>-specs` project
57
- and starts chatting there. Airlock MCP covers building specs, using specs to
58
- pull and push governed data, and improving specs from real use cases.
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.
59
74
 
60
75
  For Codex, the install command should register the server with:
61
76
 
@@ -67,12 +82,18 @@ The MCP-style flow should be:
67
82
 
68
83
  1. Run `npx @reunionstudio/airlock-mcp install` once for the agent environment.
69
84
  2. Open Codex.
70
- 3. Create a new blank project named for the org or project, such as
85
+ 3. Create or open a Git-backed specs repo named for the org or project, such as
71
86
  `home-specs`.
72
- 4. Tell Codex: `Use Airlock to help me improve a process with specs.`
73
- 5. Airlock MCP welcomes, asks what process the user wants to improve, explains
74
- the loop around the process, and proposes a small first spec plus a plan for
75
- more.
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 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.
76
97
 
77
98
  `init-repo` creates `AGENTS.md`, `.agents/skills/airlock-mcp/SKILL.md`, and
78
99
  `workspaces/`. That is what lets Codex understand prompts such as
@@ -98,9 +119,101 @@ websites, APIs, data feeds, or physical events. Actions may go back through
98
119
  those same places. After giving examples, call these places interfaces: where
99
120
  the process observes from or acts through.
100
121
 
122
+ Ask whether the user already has artifacts for the process: CSV or Excel files,
123
+ JSON samples, API docs, schemas, forms, screenshots, PDFs, exports, message
124
+ examples, or other content people already use. Treat these as design artifacts
125
+ for drafting the spec. They are different from Airlock attachments, which are
126
+ evidence files submitted later with governed records.
127
+
128
+ When useful, point at the reusable `airlock-specs` library for starting points,
129
+ patterns, and ideas. Do not treat those library specs as guaranteed to reflect
130
+ the current shape of third-party systems. If current API docs, exports, samples,
131
+ schemas, or user-provided artifacts disagree with the library, prefer the
132
+ current artifacts and record the divergence.
133
+
101
134
  Ask for the messy version. Help turn it into a small first Airlock spec and a
102
135
  plan for more.
103
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
+
104
217
  ## Start From Feedback
105
218
 
106
219
  Use this when the user chooses a shared feedback loop for humans and agents.
@@ -144,6 +257,11 @@ Supported input shapes:
144
257
  - a raw canonical object with `core_config` or `column_config`
145
258
  - a legacy `{"specs": [{"config": ...}]}`
146
259
 
260
+ Spec-library imports are starting points, not live system contracts. Before
261
+ using one for a third-party system, compare it with current API docs, CSV or
262
+ Excel exports, JSON samples, schemas, forms, and other real artifacts. Override
263
+ outdated or mismatched fields when current evidence says the shape has changed.
264
+
147
265
  ## Clone A Workspace
148
266
 
149
267
  Use this when a related spec should preserve most design choices but needs a new
@@ -187,7 +305,10 @@ airlock-mcp next workspaces/team-posts
187
305
 
188
306
  The summary shows the current spec identity, required fields, variant fields,
189
307
  attachment posture, guest access model, sample record count, note-file status,
190
- and local check counts.
308
+ and local check counts. It is organized as a structured spec card with sections
309
+ for core, file rules, attachments, guest access, column rules, samples, notes,
310
+ and check status so Codex can present the draft back to the user before asking
311
+ for decisions.
191
312
 
192
313
  `next` prints the same recap plus a conservative next action: fix local errors,
193
314
  review warnings, fill open design prompts in `decisions.md`, add sample records,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reunionstudio/airlock-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Single-install MCP interface for Airlock agents.",
5
5
  "type": "module",
6
6
  "repository": {
package/setup.py CHANGED
@@ -27,7 +27,7 @@ def data_files():
27
27
 
28
28
  setup(
29
29
  name="airlock-mcp",
30
- version="0.1.1",
30
+ version="0.1.3",
31
31
  description="Codex-first workbench and CLI for drafting Airlock specs.",
32
32
  packages=find_packages("src"),
33
33
  package_dir={"": "src"},
@@ -1,3 +1,3 @@
1
1
  """Airlock MCP CLI package."""
2
2
 
3
- __version__ = "0.1.1"
3
+ __version__ = "0.1.3"
@@ -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)
@@ -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
- and discovering improvements from real use cases. This project repo remains
20
- the durable memory for the specs being drafted.
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
 
@@ -29,6 +30,13 @@ Reserve `airlock-specs` for the canonical reusable Airlock spec library. A
29
30
  team, customer, domain, or project should use its own scoped repo name such as
30
31
  `home-specs`, `acme-finance-specs`, or `customer-onboarding-specs`.
31
32
 
33
+ Specs work should live in a real version-controlled repo. Prefer GitHub when
34
+ the user already uses GitHub, but any normal Git host is acceptable. Before
35
+ creating a new `<slug>-specs` directory, ask where it should live. If Codex can
36
+ create the repo, offer to initialize git and create or push the GitHub repo. If
37
+ not, ask the user to create the repo and open it in Codex before bootstrapping
38
+ Airlock MCP files.
39
+
32
40
  Airlock itself does not create this repo. Codex can create it locally while
33
41
  helping the user start an Airlock MCP project, then Airlock receives the
34
42
  finished spec later. Start in Codex, not Snowflake Cortex. Snow CLI or Cortex
@@ -38,7 +46,13 @@ installed Airlock app.
38
46
  ## Starting A Spec Project
39
47
 
40
48
  After bootstrap, welcome the user and orient before creating a workspace. Start
41
- with:
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:
42
56
 
43
57
  What process do you want to improve?
44
58
 
@@ -55,15 +69,65 @@ websites, APIs, data feeds, or physical events. Actions may go back through
55
69
  those same places. After giving examples, call these places interfaces: where
56
70
  the process observes from or acts through.
57
71
 
72
+ Ask whether the user already has artifacts for the process: CSV or Excel files,
73
+ JSON samples, API docs, schemas, forms, screenshots, PDFs, exports, message
74
+ examples, or other defined content people already use. A small real sample is
75
+ often better than a long explanation. Remind the user to redact secrets before
76
+ attaching files or pasting content.
77
+
78
+ Use `airlock-specs` as a reusable library of starting points, patterns, and
79
+ ideas when it is available. Do not assume library specs match the current shape
80
+ of a third-party API, export, or business object. Prefer current API docs, real
81
+ data exports, samples, and user-provided artifacts when they conflict with the
82
+ library, and record the reason for the divergence.
83
+
58
84
  Ask for the messy version. Help turn it into a small first Airlock spec and a
59
85
  plan for more. Do not create the first workspace until the user chooses a path.
60
86
  Create `posts` only when the user wants a shared feedback loop or explicitly
61
87
  asks for the posts pattern.
62
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
+
63
126
  ## Working Style
64
127
 
65
128
  - Use the repo-scoped `$airlock-mcp` skill for spec drafting, review, and
66
- pattern selection.
129
+ pattern selection, or for app/workflow implementation that uses existing
130
+ Airlock specs.
67
131
  - Keep drafts small and concrete. Prefer one useful governed output over a
68
132
  large speculative process map, then keep a plan for later specs.
69
133
  - Preserve decisions in workspace files so future Codex sessions can resume
@@ -91,6 +155,9 @@ Resolve these before final JSON:
91
155
  - references and expectations
92
156
  - delegation and agent identity
93
157
  - interfaces observed from or acted through
158
+ - existing artifacts such as CSV, Excel, JSON, API docs, schemas, exports,
159
+ forms, screenshots, PDFs, or message examples
160
+ - whether an `airlock-specs` library pattern was used, changed, or rejected
94
161
  - likely gaps in observations, orientation, decisions, or actions
95
162
  - observe-orient-decide-act loop
96
163
 
@@ -168,8 +235,9 @@ def format_bootstrap_result(result: BootstrapResult) -> str:
168
235
  "",
169
236
  "next:",
170
237
  "1. Open this repo in Codex.",
171
- "2. Ask: Use Airlock to help me build and use specs.",
172
- "3. Start with: What process do you want to improve?",
238
+ "2. Keep this specs project in git; GitHub is recommended when available.",
239
+ "3. Ask: Use Airlock to help me build and use specs.",
240
+ "4. Start with: What process do you want to improve?",
173
241
  ]
174
242
  )
175
243
  return "\n".join(lines)