@oxy-hq/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +163 -0
- package/dist/main.mjs +6688 -0
- package/dist/main.mjs.map +1 -0
- package/dist/mcp-VdontkUr.mjs +316 -0
- package/dist/mcp-VdontkUr.mjs.map +1 -0
- package/json-schemas/agent-test.json +104 -0
- package/json-schemas/agentic.json +406 -0
- package/json-schemas/app.json +1884 -0
- package/json-schemas/config.json +2215 -0
- package/json-schemas/flow-test.json +124 -0
- package/json-schemas/global-semantics.json +782 -0
- package/json-schemas/workflow.json +1907 -0
- package/package.json +65 -0
- package/skills/oxy-airway-pipelines/SKILL.md +338 -0
- package/skills/oxy-automations/SKILL.md +67 -0
- package/skills/oxy-cli/SKILL.md +134 -0
- package/skills/oxy-custom-apps/SKILL.md +330 -0
- package/skills/oxy-data-architecture/SKILL.md +225 -0
- package/skills/oxy-semantic-modeling/SKILL.md +216 -0
- package/template/.gitattributes +2 -0
- package/template/.github/scripts/ci-publish.sh +77 -0
- package/template/.github/scripts/discover-apps.sh +175 -0
- package/template/.github/scripts/publish-env.sh +55 -0
- package/template/.github/workflows/publish.yaml +653 -0
- package/template/.github/workflows/validate.yaml +264 -0
- package/template/.nvmrc +1 -0
- package/template/.oxyc-managed +297 -0
- package/template/README.md +414 -0
- package/template/_gitignore +32 -0
- package/template/apps/.gitkeep +0 -0
- package/template/config.yml +53 -0
- package/template/memory/.gitkeep +0 -0
- package/template/package.json +17 -0
- package/template/pipelines/.gitkeep +0 -0
- package/template/pnpm-workspace.yaml +32 -0
- package/template/scripts/dev.sh +443 -0
- package/template/semantics/topics/.gitkeep +0 -0
- package/template/semantics/views/.gitkeep +0 -0
- package/template/workflows/.gitkeep +0 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# Validate __NAME__'s semantic model on every pull request.
|
|
2
|
+
#
|
|
3
|
+
# `oxy compile` is the gate. It parses every workspace YAML file and writes the
|
|
4
|
+
# result into a Postgres schema, so this job needs a throwaway database — and
|
|
5
|
+
# it needs one more thing that is easy to miss: a row in `workspaces` for the
|
|
6
|
+
# workspace id it compiles under. That row is created at `oxy serve --local`
|
|
7
|
+
# boot, NOT by migrations, so a CI job that only migrates dies on a foreign-key
|
|
8
|
+
# violation *before reading a single line of YAML*.
|
|
9
|
+
#
|
|
10
|
+
# Every step below was run against a real Postgres 16 container before it was
|
|
11
|
+
# written here, on the pinned CLI version. If you change a step, run it — and
|
|
12
|
+
# run it in BOTH layouts, since `OXY_WORKSPACE` below makes this file one that
|
|
13
|
+
# has two correct answers rather than one.
|
|
14
|
+
#
|
|
15
|
+
# What this gate catches and what it does not: see "What CI checks" in README.md.
|
|
16
|
+
|
|
17
|
+
name: validate
|
|
18
|
+
|
|
19
|
+
on:
|
|
20
|
+
pull_request:
|
|
21
|
+
# Also on a push to the default branch, and the reason is not belt-and-braces.
|
|
22
|
+
# With `pull_request` alone this gate never ran at all on the workflow this
|
|
23
|
+
# template actually ships with: a scaffolded repo starts unprotected, work
|
|
24
|
+
# goes straight to main, and the compile check sat there being triggered by
|
|
25
|
+
# nothing. It stays useful once PRs are required too — a merge is a push, and
|
|
26
|
+
# a branch protected today can be unprotected tomorrow.
|
|
27
|
+
#
|
|
28
|
+
# Deliberately no `paths:` filter. `oxy compile` reads the whole workspace,
|
|
29
|
+
# and a filter that missed a path would skip validation silently, which is
|
|
30
|
+
# worse than compiling on a README change.
|
|
31
|
+
push:
|
|
32
|
+
branches: [main]
|
|
33
|
+
|
|
34
|
+
# This gate reads the tree and compiles it into a throwaway database. It never
|
|
35
|
+
# writes to the repository, so it does not need a token that can. Stated
|
|
36
|
+
# explicitly because the DEFAULT is whatever the repo or org is set to, which
|
|
37
|
+
# for many repos is read/write on every scope.
|
|
38
|
+
permissions:
|
|
39
|
+
contents: read
|
|
40
|
+
|
|
41
|
+
env:
|
|
42
|
+
# Pinned deliberately. `get.oxy.tech` honours OXY_VERSION, and this is the
|
|
43
|
+
# version the sequence below was proven against — bump it only after
|
|
44
|
+
# re-running the sequence, not on sight.
|
|
45
|
+
OXY_VERSION: "0.5.126"
|
|
46
|
+
|
|
47
|
+
# WHERE THE OXY WORKSPACE IS, relative to the repo root.
|
|
48
|
+
#
|
|
49
|
+
# . the repo IS the workspace — every repo `oxyc new` scaffolds
|
|
50
|
+
# oxy the repo CONTAINS one — the shape of every repo that was imported
|
|
51
|
+
#
|
|
52
|
+
# Substituted at render time from `__WORKSPACE__`, by the same machinery that
|
|
53
|
+
# writes __SLUG__ and __NAME__, so `oxyc update` re-renders it and there is no
|
|
54
|
+
# special case anywhere for a repo shaped the second way.
|
|
55
|
+
#
|
|
56
|
+
# It exists because this gate cannot be layout-agnostic and must not be
|
|
57
|
+
# layout-wrong: `oxy compile` at the repo root of a repo whose workspace is
|
|
58
|
+
# `oxy/` reads no config.yml, compiles nothing, and fails. A CI file that
|
|
59
|
+
# installs cleanly and then fails on every run is worse than one that was
|
|
60
|
+
# never installed.
|
|
61
|
+
#
|
|
62
|
+
# The TOOLING adapts to the repo, never the other way round. The platform
|
|
63
|
+
# registered this subdirectory when the workspace was onboarded and there is
|
|
64
|
+
# no UI or CLI to change it, so flattening a repo to match this template
|
|
65
|
+
# would silently break its registration with no repair short of re-onboarding.
|
|
66
|
+
OXY_WORKSPACE: "__WORKSPACE__"
|
|
67
|
+
|
|
68
|
+
jobs:
|
|
69
|
+
compile:
|
|
70
|
+
name: oxy compile
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
|
|
73
|
+
services:
|
|
74
|
+
postgres:
|
|
75
|
+
image: postgres:16
|
|
76
|
+
env:
|
|
77
|
+
POSTGRES_PASSWORD: ci
|
|
78
|
+
ports:
|
|
79
|
+
- 5432:5432
|
|
80
|
+
options: >-
|
|
81
|
+
--health-cmd "pg_isready -U postgres"
|
|
82
|
+
--health-interval 5s
|
|
83
|
+
--health-timeout 5s
|
|
84
|
+
--health-retries 10
|
|
85
|
+
|
|
86
|
+
env:
|
|
87
|
+
OXY_DATABASE_URL: postgres://postgres:ci@localhost:5432/postgres
|
|
88
|
+
# OXY_STATE_DIR is set per step, not here: the `runner` context is not
|
|
89
|
+
# available in a job-level `env:` block, and the expression would expand
|
|
90
|
+
# to the empty string — putting the CLI's state dir at "/oxy-state".
|
|
91
|
+
# actionlint catches this; a job that "just ran" would not.
|
|
92
|
+
|
|
93
|
+
steps:
|
|
94
|
+
- uses: actions/checkout@v4
|
|
95
|
+
with:
|
|
96
|
+
# This job runs a downloaded installer script. Without this, checkout
|
|
97
|
+
# leaves a GITHUB_TOKEN in .git/config for that script to find. The
|
|
98
|
+
# job-level `permissions: contents: read` already narrows what such a
|
|
99
|
+
# token could do; not persisting it at all is the cheaper half of the
|
|
100
|
+
# same argument, and it costs nothing here — nothing in this job
|
|
101
|
+
# pushes.
|
|
102
|
+
persist-credentials: false
|
|
103
|
+
|
|
104
|
+
- name: Install the pinned oxy CLI
|
|
105
|
+
run: |
|
|
106
|
+
set -euo pipefail
|
|
107
|
+
# NOT `bash <(curl ...)`, the form the docs use: process substitution
|
|
108
|
+
# hands bash an empty file when the download fails, and bash then
|
|
109
|
+
# exits 0 — a step that reports "installed" having installed nothing.
|
|
110
|
+
# Download to a file first so curl's own -f failure is the step's.
|
|
111
|
+
curl -sSfL https://get.oxy.tech -o "$RUNNER_TEMP/get-oxy.sh"
|
|
112
|
+
OXY_VERSION="$OXY_VERSION" bash "$RUNNER_TEMP/get-oxy.sh"
|
|
113
|
+
|
|
114
|
+
# And check the installer left something behind: it downloads the
|
|
115
|
+
# binary with `curl -L` (no -f) and exits 0 regardless.
|
|
116
|
+
if [ ! -x "$HOME/.local/bin/oxy" ]; then
|
|
117
|
+
echo "::error::the installer exited 0 but left no executable at $HOME/.local/bin/oxy." >&2
|
|
118
|
+
exit 1
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
# The installer drops the binary in ~/.local/bin for a non-root user
|
|
122
|
+
# and edits shell rc files, which a GitHub step never reads. Put it
|
|
123
|
+
# on PATH explicitly instead of hoping.
|
|
124
|
+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
125
|
+
|
|
126
|
+
- name: Verify the oxy CLI
|
|
127
|
+
run: |
|
|
128
|
+
set -euo pipefail
|
|
129
|
+
# Not ceremony. Two silent failures make this load-bearing:
|
|
130
|
+
# 1. the installer runs `curl -L` WITHOUT -f, so a tag that does
|
|
131
|
+
# not exist writes GitHub's 404 HTML page to the binary path,
|
|
132
|
+
# chmod +x's it, and still exits 0;
|
|
133
|
+
# 2. this CLI falls through to `--help` with exit 0 on a subcommand
|
|
134
|
+
# it does not have, so an old binary "succeeds" at doing nothing.
|
|
135
|
+
if ! raw="$(oxy --version 2>&1)"; then
|
|
136
|
+
echo "::error::'oxy --version' failed — the installed binary is not runnable." >&2
|
|
137
|
+
printf '%s\n' "$raw" >&2
|
|
138
|
+
exit 1
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
have="$(printf '%s\n' "$raw" | sed -n 's/^oxy-app version: \([0-9][0-9.]*\).*/\1/p' | head -1)"
|
|
142
|
+
if [ -z "$have" ]; then
|
|
143
|
+
echo "::error::could not read a version out of 'oxy --version' — the install probably wrote a 404 page." >&2
|
|
144
|
+
printf '%s\n' "$raw" >&2
|
|
145
|
+
exit 1
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
oldest="$(printf '%s\n%s\n' "$OXY_VERSION" "$have" | sort -t. -k1,1n -k2,2n -k3,3n | head -1)"
|
|
149
|
+
if [ "$oldest" != "$OXY_VERSION" ]; then
|
|
150
|
+
echo "::error::oxy $have is older than the pinned $OXY_VERSION." >&2
|
|
151
|
+
exit 1
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
echo "oxy $have (pin $OXY_VERSION)"
|
|
155
|
+
|
|
156
|
+
- name: Migrate the throwaway database
|
|
157
|
+
env:
|
|
158
|
+
OXY_STATE_DIR: ${{ runner.temp }}/oxy-state
|
|
159
|
+
run: oxy migrate
|
|
160
|
+
|
|
161
|
+
- name: Seed the workspace row oxy compile needs
|
|
162
|
+
run: |
|
|
163
|
+
set -euo pipefail
|
|
164
|
+
# `oxy compile --workspace-id` defaults to the nil UUID, and inserting
|
|
165
|
+
# a revision for it needs the matching `workspaces` row to exist.
|
|
166
|
+
# Migrations do not create it. Seeding it here is one INSERT against a
|
|
167
|
+
# database that is destroyed with the job; the alternative — booting
|
|
168
|
+
# `oxy serve --local` just to trigger its boot-time seed — costs ~25s,
|
|
169
|
+
# binds two ports, and has no "seed and exit" mode.
|
|
170
|
+
if ! command -v psql >/dev/null 2>&1; then
|
|
171
|
+
echo "::error::psql is not on this runner. GitHub's ubuntu-latest image ships it; a self-hosted runner may not. Install postgresql-client, or run this INSERT with: docker exec ${{ job.services.postgres.id }} psql -U postgres" >&2
|
|
172
|
+
exit 1
|
|
173
|
+
fi
|
|
174
|
+
psql "$OXY_DATABASE_URL" -v ON_ERROR_STOP=1 -q -c \
|
|
175
|
+
"INSERT INTO workspaces (id, name) VALUES ('00000000-0000-0000-0000-000000000000', 'ci-validate') ON CONFLICT (id) DO NOTHING;"
|
|
176
|
+
|
|
177
|
+
- name: Compile the workspace
|
|
178
|
+
env:
|
|
179
|
+
OXY_STATE_DIR: ${{ runner.temp }}/oxy-state
|
|
180
|
+
run: |
|
|
181
|
+
set -euo pipefail
|
|
182
|
+
|
|
183
|
+
# --workspace-path MUST be absolute. See the floor check below.
|
|
184
|
+
#
|
|
185
|
+
# $OXY_WORKSPACE is repo-RELATIVE, so it is joined to $GITHUB_WORKSPACE
|
|
186
|
+
# and then CANONICALISED rather than concatenated: `cd` + `pwd -P`
|
|
187
|
+
# collapses the trailing `/.` that a root workspace would otherwise
|
|
188
|
+
# leave in the path, and it fails HERE, naming the directory, instead
|
|
189
|
+
# of handing oxy a path that is not there. One expression, correct for
|
|
190
|
+
# a repo that is the workspace and for one that contains it.
|
|
191
|
+
ws=""
|
|
192
|
+
if ! ws="$(cd "$GITHUB_WORKSPACE/$OXY_WORKSPACE" 2>/dev/null && pwd -P)"; then
|
|
193
|
+
echo "::error::no directory at \$GITHUB_WORKSPACE/$OXY_WORKSPACE. OXY_WORKSPACE says where this repo's Oxy workspace is, relative to the repo root: '.' when the repo IS the workspace, a subdirectory name when it contains one." >&2
|
|
194
|
+
exit 1
|
|
195
|
+
fi
|
|
196
|
+
echo "workspace: $ws (OXY_WORKSPACE=$OXY_WORKSPACE)"
|
|
197
|
+
|
|
198
|
+
status=0
|
|
199
|
+
oxy compile --workspace-path "$ws" --skip-migrations --json \
|
|
200
|
+
>"$RUNNER_TEMP/compile.stdout" 2>"$RUNNER_TEMP/compile.stderr" || status=$?
|
|
201
|
+
|
|
202
|
+
cat "$RUNNER_TEMP/compile.stderr" >&2
|
|
203
|
+
|
|
204
|
+
# `--json` does NOT put only JSON on stdout: a human-readable
|
|
205
|
+
# "Compiling workspace id=... path=..." line comes first, so piping
|
|
206
|
+
# stdout straight into jq fails ("Invalid numeric literal", exit 4).
|
|
207
|
+
# Extract from the line that opens the JSON object instead.
|
|
208
|
+
sed -n '/^{/,$p' "$RUNNER_TEMP/compile.stdout" > "$RUNNER_TEMP/compile.json"
|
|
209
|
+
|
|
210
|
+
if [ ! -s "$RUNNER_TEMP/compile.json" ]; then
|
|
211
|
+
echo "::error::oxy compile printed no JSON — it failed before compiling anything (database unreachable, or no workspaces row for --workspace-id)." >&2
|
|
212
|
+
exit 1
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
# An empty .failures array is a legitimate result and jq exits 0 for
|
|
216
|
+
# it, so no `|| true` is needed — and `|| true` here would be wrong,
|
|
217
|
+
# since it would also swallow a real jq failure.
|
|
218
|
+
jq -r '.failures[] | "::error file=\(.path)::[\(.kind)] \(.message)"' "$RUNNER_TEMP/compile.json"
|
|
219
|
+
jq -r '"compile: status=\(.status) seen=\(.file_count_seen) compiled=\(.file_count_compiled) failed=\(.file_count_failed)"' "$RUNNER_TEMP/compile.json"
|
|
220
|
+
|
|
221
|
+
# Floor check, and it earns its place: `oxy compile --workspace-path .`
|
|
222
|
+
# finds config.yml and nothing else — every recursive kind is globbed
|
|
223
|
+
# as "<root>/**/*.view.yml", which matches nothing when <root> is "."
|
|
224
|
+
# — and it exits 0 while doing it. A green gate that validated no
|
|
225
|
+
# views is worse than no gate, so assert the walker saw at least the
|
|
226
|
+
# semantic files that are on disk.
|
|
227
|
+
seen="$(jq -r '.file_count_seen' "$RUNNER_TEMP/compile.json")"
|
|
228
|
+
floor=0
|
|
229
|
+
# Under $ws, not the checkout directory. In a repo whose workspace is
|
|
230
|
+
# `oxy/` the root has no config.yml, so a root-relative floor would
|
|
231
|
+
# sit one lower than the truth — quietly agreeing with a walk that saw
|
|
232
|
+
# nothing, which is the exact silence this check exists to break.
|
|
233
|
+
if [ -f "$ws/config.yml" ]; then
|
|
234
|
+
floor=$((floor + 1))
|
|
235
|
+
fi
|
|
236
|
+
# Count the WHOLE workspace tree, not just semantics/: the walker
|
|
237
|
+
# globs `**/*.view.yml` from the workspace root, so a view under
|
|
238
|
+
# models/ counts too, and a floor that only looked at semantics/ would
|
|
239
|
+
# sit at 1 and agree with a walk that saw nothing — the same silent
|
|
240
|
+
# agreement it exists to catch. Rooted at $ws for the same reason the
|
|
241
|
+
# config.yml check is: what the walker will descend into is the
|
|
242
|
+
# workspace, and a repo that contains one may hold .view.yml files
|
|
243
|
+
# outside it (a vendored fixture, an example) that the walker will
|
|
244
|
+
# never see and that would make this floor unreachably high.
|
|
245
|
+
# The prunes mirror the walker's own skip list; pruning a directory
|
|
246
|
+
# the walker would have descended can only UNDER-count, and a floor
|
|
247
|
+
# that is too low never fails a healthy repo.
|
|
248
|
+
# find prints nothing and exits 0 when there is no match, which is
|
|
249
|
+
# this repo's day-one state — a legitimate result, not an error to
|
|
250
|
+
# swallow. (On that empty tree floor=1=seen, so the check has nothing
|
|
251
|
+
# to detect and correctly says nothing.)
|
|
252
|
+
n="$(find "$ws" \( -name .git -o -name node_modules -o -name target \
|
|
253
|
+
-o -name dist -o -name build -o -name .oxy_state \
|
|
254
|
+
-o -name .semantics -o -name .preagg -o -name .worktrees \
|
|
255
|
+
-o -name .cubestore -o -name .airlayer_cache \) -prune \
|
|
256
|
+
-o -type f \( -name '*.view.yml' -o -name '*.topic.yml' \) \
|
|
257
|
+
! -name '*.test.*' -print | wc -l | tr -d ' ')"
|
|
258
|
+
floor=$((floor + n))
|
|
259
|
+
if [ "$seen" -lt "$floor" ]; then
|
|
260
|
+
echo "::error::oxy compile saw $seen file(s) but at least $floor are on disk under $ws — the walker did not descend into the workspace. Check that --workspace-path is absolute, and that OXY_WORKSPACE ('$OXY_WORKSPACE') names the directory holding config.yml." >&2
|
|
261
|
+
exit 1
|
|
262
|
+
fi
|
|
263
|
+
|
|
264
|
+
exit "$status"
|
package/template/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
20
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# Who owns each file in a repo scaffolded from this template, and which of a
|
|
2
|
+
# repo's two trees each one describes.
|
|
3
|
+
#
|
|
4
|
+
# `oxyc update <customer>` brings an existing customer repo back in line with
|
|
5
|
+
# `template/`, and the only question that matters when it does is which files it
|
|
6
|
+
# may rewrite. The live customer repos are not scaffolds any more: one of them
|
|
7
|
+
# carries 7 views, 4 topics, a pipeline, an app and five memory facts, and its
|
|
8
|
+
# `package.json` differs from this template's because the customer added
|
|
9
|
+
# dependencies for that app. A sync that overwrote what it did not own would
|
|
10
|
+
# have eaten all of it.
|
|
11
|
+
#
|
|
12
|
+
# So the answer is written down HERE, next to the files, where whoever adds the
|
|
13
|
+
# next template file has to walk past it. It was written a phase BEFORE the
|
|
14
|
+
# command that reads it, on the same bet as `.oxyc-template.json`: the
|
|
15
|
+
# classification is cheap to record now and impossible to recover later, once
|
|
16
|
+
# repos exist that nobody can classify from memory.
|
|
17
|
+
#
|
|
18
|
+
# lib/sync.sh is the reader, and it decides nothing on its own — every role
|
|
19
|
+
# below is taken from this file and `unmatched` is the default it falls back to.
|
|
20
|
+
#
|
|
21
|
+
# --- format -----------------------------------------------------------------
|
|
22
|
+
#
|
|
23
|
+
# <directive> <glob> one per line, whitespace-separated
|
|
24
|
+
# # ... comment, and blank lines are ignored
|
|
25
|
+
#
|
|
26
|
+
# The glob is matched against a path relative to the repo root, with a shell
|
|
27
|
+
# `case`, so `*` spans `/` — `.github/workflows/*` covers any depth beneath it
|
|
28
|
+
# and `*.gitkeep` covers every placeholder wherever it sits.
|
|
29
|
+
#
|
|
30
|
+
# There are TWO families of directive and they answer different questions:
|
|
31
|
+
#
|
|
32
|
+
# managed / scaffold / mixed who owns the bytes
|
|
33
|
+
# scope-repo / scope-workspace which tree the file describes
|
|
34
|
+
#
|
|
35
|
+
# Each family has its own reader with its own allowlist, so a line from one is
|
|
36
|
+
# invisible to the other and a typo in either (`manged`, `scope-wrokspace`) is
|
|
37
|
+
# invisible to both — it falls to that family's default and fails the
|
|
38
|
+
# completeness test in tests/template_ownership.bats rather than quietly
|
|
39
|
+
# classifying a file under a word nothing recognises.
|
|
40
|
+
#
|
|
41
|
+
# --- the three roles, and what a wrong one costs ----------------------------
|
|
42
|
+
#
|
|
43
|
+
# managed The TOOL owns the bytes. A sync may overwrite the file outright,
|
|
44
|
+
# and a customer edit to it is a mistake that a sync is entitled to
|
|
45
|
+
# undo. Classifying wrongly here is DATA LOSS.
|
|
46
|
+
#
|
|
47
|
+
# scaffold Written once by `oxyc new` and never touched again. The customer
|
|
48
|
+
# owns it from the moment it lands. A sync may report that it has
|
|
49
|
+
# drifted; it may not rewrite it. Classifying wrongly here costs
|
|
50
|
+
# someone a manual copy — which is why anything genuinely uncertain
|
|
51
|
+
# belongs in this row rather than in `managed`.
|
|
52
|
+
#
|
|
53
|
+
# mixed Both, in different PARTS of one file. A sync must merge, never
|
|
54
|
+
# replace, and until it can do that surgically it must report and
|
|
55
|
+
# leave the file alone.
|
|
56
|
+
#
|
|
57
|
+
# That is the rule for a file that is THERE, and it is the only rule
|
|
58
|
+
# `oxyc update` has: a repo the tool scaffolded was given both halves
|
|
59
|
+
# on day one, so a mixed file missing from it was deleted on purpose
|
|
60
|
+
# and restoring it would undo somebody's decision. Update also runs
|
|
61
|
+
# over and over, and a command you run weekly must not quietly
|
|
62
|
+
# recreate what you removed.
|
|
63
|
+
#
|
|
64
|
+
# `oxyc adopt` meets the other case, and answers it differently. An
|
|
65
|
+
# imported repo never had the file, so there is no customer half to
|
|
66
|
+
# protect — and withholding it strands the managed files it pairs
|
|
67
|
+
# with. The first real adopt did exactly that: pokehouse-oxy got
|
|
68
|
+
# `scripts/dev.sh` and no package.json, so the `"dev"` entry that
|
|
69
|
+
# runs the script was never created and `pnpm dev` could not work.
|
|
70
|
+
# It installed the tool and withheld the handle.
|
|
71
|
+
#
|
|
72
|
+
# So adopt — a one-time act somebody types on purpose — installs a
|
|
73
|
+
# mixed file that is ABSENT and still never touches one that is
|
|
74
|
+
# PRESENT. Per FILE, not per role: pokehouse has a .gitignore, and
|
|
75
|
+
# that one is left exactly alone.
|
|
76
|
+
#
|
|
77
|
+
# --- and the role that is never written down: everything else ---------------
|
|
78
|
+
#
|
|
79
|
+
# unmatched The CUSTOMER'S. A sync must not rewrite it, must not delete it,
|
|
80
|
+
# and must not ask — never touch it.
|
|
81
|
+
#
|
|
82
|
+
# That is a DEFAULT, not an omission, and it is the single most important rule
|
|
83
|
+
# here. A scaffolded repo becomes mostly files this template never had: views,
|
|
84
|
+
# topics, pipelines, an app bundle, pnpm-lock.yaml, memory facts. Every one of
|
|
85
|
+
# them is unmatched BY CONSTRUCTION, and the file a sync does not recognise is
|
|
86
|
+
# exactly the file it did not create.
|
|
87
|
+
#
|
|
88
|
+
# So the safe direction is built into the shape rather than remembered: a rule
|
|
89
|
+
# has to be WRITTEN before anything may be rewritten, and forgetting to write
|
|
90
|
+
# one can only ever make a sync do less.
|
|
91
|
+
#
|
|
92
|
+
# `mixed` exists because of one concrete case, and it is worth stating: the
|
|
93
|
+
# root `package.json` holds a `"dev"` script entry that runs `scripts/dev.sh`.
|
|
94
|
+
# That entry is tool-owned — it is the entry point to a tool-owned script, and
|
|
95
|
+
# a customer deleting it has broken the local dev flow — while the
|
|
96
|
+
# `dependencies` beside it are the customer's app's, and are exactly what a
|
|
97
|
+
# blind overwrite would destroy. Two owners, one file. A two-way split would
|
|
98
|
+
# have forced a choice between losing the entry and losing the dependencies.
|
|
99
|
+
#
|
|
100
|
+
# --- the second axis: which of a repo's TWO trees does a file describe? -----
|
|
101
|
+
#
|
|
102
|
+
# A role says who owns a file's bytes. It does not say what the file is ABOUT,
|
|
103
|
+
# and in a repo `oxyc new` scaffolded there was never a difference to say: the
|
|
104
|
+
# repo IS the workspace, so every path above is simply "at the root".
|
|
105
|
+
#
|
|
106
|
+
# An IMPORTED repo splits that in two, and the template's files land on both
|
|
107
|
+
# sides of the split:
|
|
108
|
+
#
|
|
109
|
+
# config.yml and README.md describe the OXY WORKSPACE. pokehouse-oxy keeps
|
|
110
|
+
# its workspace in `oxy/`, where a config.yml and a readme already sit,
|
|
111
|
+
# written by somebody before oxyc saw the repo. The template's copies
|
|
112
|
+
# describe nothing at that repo's root — and writing them there would build a
|
|
113
|
+
# second, half-populated workspace beside the real one.
|
|
114
|
+
#
|
|
115
|
+
# package.json, pnpm-workspace.yaml and .gitignore describe the REPO ROOT,
|
|
116
|
+
# and are not Oxy workspace files at all. The first two are the PNPM
|
|
117
|
+
# workspace — a different workspace, which is where `scripts/dev.sh` is
|
|
118
|
+
# installed and where its `"dev"` entry has to live — and the third is the
|
|
119
|
+
# git working tree's ignore rules. All three belong at the root in both
|
|
120
|
+
# layouts, exactly as the managed machinery does.
|
|
121
|
+
#
|
|
122
|
+
# That distinction had nowhere to be written down, so lib/sync.sh inferred it
|
|
123
|
+
# from the ROLE — `scaffold` and `mixed` were skipped together as "the
|
|
124
|
+
# workspace's" — and the inference was wrong for all three of the second group.
|
|
125
|
+
# pokehouse-oxy is what that cost. Adopted before the mixed-and-absent rule
|
|
126
|
+
# landed, it has `scripts/dev.sh` and no package.json; adopt then refused it as
|
|
127
|
+
# already-stamped, and update called package.json a workspace file and would
|
|
128
|
+
# not so much as name the gap. The file could not arrive by any path.
|
|
129
|
+
#
|
|
130
|
+
# So it is written down, on its own lines, in its own vocabulary:
|
|
131
|
+
#
|
|
132
|
+
# scope-repo describes the REPO ROOT. Always in scope.
|
|
133
|
+
# scope-workspace describes the OXY WORKSPACE. Out of scope in a repo that
|
|
134
|
+
# keeps its workspace in a subdirectory; identical to
|
|
135
|
+
# scope-repo in a repo that IS its workspace.
|
|
136
|
+
#
|
|
137
|
+
# A SECOND AXIS, and not a fourth role or a modifier on the three. The two
|
|
138
|
+
# questions are independent — package.json is `mixed` AND repo-scoped,
|
|
139
|
+
# config.yml is `scaffold` AND workspace-scoped — so a file that could answer
|
|
140
|
+
# only one of them would lose the other. A fourth role would take config.yml's
|
|
141
|
+
# `scaffold` away, which is the statement that a sync may report its drift and
|
|
142
|
+
# never rewrite it, and buy nothing back. A modifier (`scaffold@workspace`)
|
|
143
|
+
# keeps one line per file and breaks every reader instead: the
|
|
144
|
+
# `case "$role" in managed)` arms in lib/sync.sh, lib/adopt.sh and lib/doctor.sh
|
|
145
|
+
# stop matching a compound token, and in adopt_plan the fall-through is
|
|
146
|
+
# `continue` — a file silently not installed, which is precisely the failure
|
|
147
|
+
# this axis exists to end.
|
|
148
|
+
#
|
|
149
|
+
# THE DEFAULT IS `scope-repo`, and it deliberately points the OTHER WAY from
|
|
150
|
+
# `unmatched` above. A wrong role costs DATA LOSS, so the role default has to
|
|
151
|
+
# be the quiet one and forgetting a role can only make a sync do less. Scope
|
|
152
|
+
# cannot cost data: it never widens what a role permits, and `scaffold` and
|
|
153
|
+
# `mixed` are not written in any layout. What scope trades is NOISE against
|
|
154
|
+
# SILENCE — and silence is the whole defect. Forget a `scope-workspace` line
|
|
155
|
+
# and a file this repo will never have is reported on every run until somebody
|
|
156
|
+
# writes it. Forget a `scope-repo` line under the opposite default and the file
|
|
157
|
+
# can never arrive and nothing anywhere says so. The unwritten answer is
|
|
158
|
+
# therefore the LOUD one.
|
|
159
|
+
#
|
|
160
|
+
# tests/template_ownership.bats requires both answers for every file the
|
|
161
|
+
# template ships, so that default is unreachable from this manifest — it
|
|
162
|
+
# governs only a manifest already failing its own test.
|
|
163
|
+
|
|
164
|
+
# --- managed: the tool's own machinery --------------------------------------
|
|
165
|
+
|
|
166
|
+
# CI. The workflows and the shell they call are versioned with the tooling,
|
|
167
|
+
# pinned together (both files carry the same OXY_VERSION and a test enforces
|
|
168
|
+
# it), and a customer editing them by hand is how a fleet of repos ends up on
|
|
169
|
+
# eight different CI definitions.
|
|
170
|
+
# The globs below match file EXTENSIONS rather than whole directories, and
|
|
171
|
+
# that is deliberate. `scripts/*` would silently absorb a `scripts/NOTES.txt`
|
|
172
|
+
# somebody adds next year into `managed` — a file nobody classified, in the
|
|
173
|
+
# data-loss direction. Narrowed, that file is simply unmatched, which is both
|
|
174
|
+
# the safe answer and a failing completeness test until a human decides.
|
|
175
|
+
managed .github/workflows/*.yaml
|
|
176
|
+
managed .github/workflows/*.yml
|
|
177
|
+
managed .github/scripts/*.sh
|
|
178
|
+
|
|
179
|
+
# Local dev. Same argument: `scripts/dev.sh` is the tooling's answer to "run an
|
|
180
|
+
# app locally", and it is meant to improve for everyone at once.
|
|
181
|
+
managed scripts/*.sh
|
|
182
|
+
|
|
183
|
+
# The merge rule for the generated memory index. It is a statement about how
|
|
184
|
+
# oxyc's own generated file behaves, so oxyc owns it.
|
|
185
|
+
managed .gitattributes
|
|
186
|
+
|
|
187
|
+
# This file.
|
|
188
|
+
managed .oxyc-managed
|
|
189
|
+
|
|
190
|
+
# Not a template file at all — `oxyc new` writes it at render time, and
|
|
191
|
+
# `oxyc update --apply` re-points it at the commit it synced from. Listed here
|
|
192
|
+
# so that the sync's right to rewrite it is recorded in the same place as
|
|
193
|
+
# everything else's, rather than being a special case buried in code.
|
|
194
|
+
managed .oxyc-template.json
|
|
195
|
+
|
|
196
|
+
# DELIBERATELY ABSENT: `.oxyc.json`, the repo's own statement about where its
|
|
197
|
+
# Oxy workspace is (see lib/workspace.sh). No rule is written for it, which
|
|
198
|
+
# means it falls to `unmatched` — the customer's, never touch — and that is
|
|
199
|
+
# exactly right twice over. It is hand-written, so a sync rewriting it would
|
|
200
|
+
# undo a decision somebody made; and it is the file that tells the sync what
|
|
201
|
+
# shape the repo is, so a sync that overwrote it would be sawing off the branch
|
|
202
|
+
# it stands on. Named here only so that the absence reads as a decision.
|
|
203
|
+
#
|
|
204
|
+
# This template does not ship one, either: a scaffolded repo is a workspace at
|
|
205
|
+
# its root, detection settles that unaided, and shipping an override would make
|
|
206
|
+
# the exception look like the norm.
|
|
207
|
+
|
|
208
|
+
# --- scaffold: the customer's from the moment it lands ----------------------
|
|
209
|
+
|
|
210
|
+
# Both are edited immediately and permanently: config.yml gains the models and
|
|
211
|
+
# databases of a real workspace, README.md gains whatever the team needs to say
|
|
212
|
+
# about this customer.
|
|
213
|
+
scaffold config.yml
|
|
214
|
+
scaffold README.md
|
|
215
|
+
|
|
216
|
+
# Node version. A single line, and the customer may legitimately move it — an
|
|
217
|
+
# app that needs a newer runtime is their call, not the tooling's. There is no
|
|
218
|
+
# tool-owned structure inside it to merge, so `mixed` would mean nothing here;
|
|
219
|
+
# a sync that forced it back would be an unrequested downgrade, so `managed` is
|
|
220
|
+
# wrong too. Report the drift and let a human decide.
|
|
221
|
+
scaffold .nvmrc
|
|
222
|
+
|
|
223
|
+
# Placeholders that exist only so git carries an empty directory. Once a real
|
|
224
|
+
# view or pipeline lands beside one, the customer usually deletes it — and a
|
|
225
|
+
# sync that recreated it would be resurrecting a file somebody removed on
|
|
226
|
+
# purpose. Written once, then never ours again.
|
|
227
|
+
scaffold *.gitkeep
|
|
228
|
+
|
|
229
|
+
# --- mixed: two owners, one file --------------------------------------------
|
|
230
|
+
|
|
231
|
+
# The case the third role exists for; see the note at the top.
|
|
232
|
+
mixed package.json
|
|
233
|
+
|
|
234
|
+
# The `.gitignore` rules this template ships are load-bearing — dropping
|
|
235
|
+
# `node_modules/` or `out/` commits a build tree on the customer's next
|
|
236
|
+
# `git add -A`, and a test in customer-tooling pins the exact rule lines — so
|
|
237
|
+
# those lines are the tool's. Anything the customer adds below them is theirs
|
|
238
|
+
# and is expected: a scratch directory, an app-specific artifact. Both at once,
|
|
239
|
+
# which is `mixed`, not a judgement call between the two.
|
|
240
|
+
mixed .gitignore
|
|
241
|
+
|
|
242
|
+
# Same shape, and the file says so itself: the `onlyBuiltDependencies` block
|
|
243
|
+
# carries the three entries the shipped app bundles need, with an instruction
|
|
244
|
+
# to ADD to the list rather than replace it. The `packages:` globs are the
|
|
245
|
+
# tool's; a fourth dependency that needs its install script run is the
|
|
246
|
+
# customer's.
|
|
247
|
+
mixed pnpm-workspace.yaml
|
|
248
|
+
|
|
249
|
+
# --- scope: which tree each file describes ----------------------------------
|
|
250
|
+
#
|
|
251
|
+
# Grouped by answer rather than written beside the role above, so that the two
|
|
252
|
+
# axes are never read as one column of five roles. Every file the template
|
|
253
|
+
# ships appears here exactly once, the same rule the roles are held to.
|
|
254
|
+
|
|
255
|
+
# THE REPO ROOT. All of the managed machinery first: CI runs from the repo root
|
|
256
|
+
# in both layouts, `.github/scripts/` is what it calls, and the stamp and this
|
|
257
|
+
# manifest describe the repo oxyc adopted rather than the workspace inside it.
|
|
258
|
+
scope-repo .github/workflows/*.yaml
|
|
259
|
+
scope-repo .github/workflows/*.yml
|
|
260
|
+
scope-repo .github/scripts/*.sh
|
|
261
|
+
scope-repo scripts/*.sh
|
|
262
|
+
scope-repo .gitattributes
|
|
263
|
+
scope-repo .oxyc-managed
|
|
264
|
+
scope-repo .oxyc-template.json
|
|
265
|
+
|
|
266
|
+
# The PNPM workspace and the git working tree, which are root things in a repo
|
|
267
|
+
# whose Oxy workspace is somewhere else. `pnpm dev` is run from the repo root;
|
|
268
|
+
# `packages: apps/*` names directories at the repo root, which is also where
|
|
269
|
+
# `.github/scripts/discover-apps.sh` globs for bundles; `.gitignore` governs
|
|
270
|
+
# what `git add -A` stages across the whole repo, `node_modules/` and `out/`
|
|
271
|
+
# included. `.nvmrc` is the node version all of that builds under.
|
|
272
|
+
scope-repo package.json
|
|
273
|
+
scope-repo pnpm-workspace.yaml
|
|
274
|
+
scope-repo .gitignore
|
|
275
|
+
scope-repo .nvmrc
|
|
276
|
+
|
|
277
|
+
# Two placeholders whose directories are also the repo's, not the workspace's.
|
|
278
|
+
# `memory/` is about the CUSTOMER and sits at the repo root in both shapes —
|
|
279
|
+
# lib/workspace.sh says so beside memory_dir, and it is the one directory oxyc
|
|
280
|
+
# creates in a repo it did not scaffold. `apps/` is where publish.yaml looks
|
|
281
|
+
# for bundles, from the repo root, and it is what pnpm-workspace.yaml declares.
|
|
282
|
+
scope-repo memory/.gitkeep
|
|
283
|
+
scope-repo apps/.gitkeep
|
|
284
|
+
|
|
285
|
+
# THE OXY WORKSPACE. config.yml IS the workspace — it is the file
|
|
286
|
+
# workspace_resolve looks for, and finding it one level down is what makes a
|
|
287
|
+
# repo a subdirectory repo in the first place — and README.md is that
|
|
288
|
+
# workspace's own readme, describing the layout below it. An imported repo has
|
|
289
|
+
# both already, inside `oxy/`, and its root README is the project's.
|
|
290
|
+
scope-workspace config.yml
|
|
291
|
+
scope-workspace README.md
|
|
292
|
+
|
|
293
|
+
# The placeholders holding open the workspace's own directories. They live
|
|
294
|
+
# beside config.yml in a scaffolded repo and inside `oxy/` in an imported one.
|
|
295
|
+
scope-workspace pipelines/.gitkeep
|
|
296
|
+
scope-workspace workflows/.gitkeep
|
|
297
|
+
scope-workspace semantics/*/.gitkeep
|