@ogygie/cli 0.19.0 → 0.21.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/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "@ogygie/cli",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "Ogygie framework CLI: deterministic scaffolding (init), governed-file upgrades (upgrade) and lockstep verification (check) for Ogygie products.",
5
5
  "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/julienTeam/Ogygie.git",
9
+ "directory": "packages/ogygie-cli"
10
+ },
6
11
  "type": "module",
7
12
  "bin": {
8
13
  "ogygie": "dist/cli.js"
@@ -17,7 +22,7 @@
17
22
  "access": "public"
18
23
  },
19
24
  "devDependencies": {
20
- "@ogygie/core": "0.19.0",
25
+ "@ogygie/core": "0.21.0",
21
26
  "@types/node": "^24.0.0",
22
27
  "esbuild": "^0.28.0",
23
28
  "tsx": "^4.20.0",
@@ -4,14 +4,13 @@
4
4
  name: ogygie-gates
5
5
  on:
6
6
  push:
7
- branches: [main]
7
+ branches: [main, "spec/**"]
8
8
  pull_request:
9
9
 
10
- # Least-privilege token: gitleaks-action reads the PR's commit list on
11
- # pull_request events; PR comments stay disabled so no write scope is needed.
10
+ # Least-privilege token: the gates only read the checkout. The one job that
11
+ # writes (speculate) raises its own permission, alone.
12
12
  permissions:
13
13
  contents: read
14
- pull-requests: read
15
14
 
16
15
  jobs:
17
16
  verifier:
@@ -34,10 +33,155 @@ jobs:
34
33
  - run: pnpm lint # any, ts-ignore, raw-db, dangerouslySetInnerHTML
35
34
  - run: pnpm test
36
35
  # Security
37
- - uses: gitleaks/gitleaks-action@v2 # committed secrets
38
- env:
39
- GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
40
- GITLEAKS_ENABLE_COMMENTS: "false"
36
+ # Committed secrets: the gitleaks binary, pinned by version and checksum.
37
+ # Its GitHub action asks for a licence key as soon as the repository
38
+ # belongs to an organization; the binary asks for nothing. A pull request
39
+ # scans its own commits and a push the ones it brings, so a leak already
40
+ # dealt with in the past does not keep the gate red.
41
+ - env:
42
+ GITLEAKS_VERSION: "8.30.1"
43
+ GITLEAKS_SHA256: "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb"
44
+ RANGE: "${{ github.event_name == 'pull_request' && format('{0}..{1}', github.event.pull_request.base.sha, github.event.pull_request.head.sha) || format('{0}..{1}', github.event.before, github.sha) }}"
45
+ run: |
46
+ curl -sSLo "$RUNNER_TEMP/gitleaks.tar.gz" "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
47
+ echo "${GITLEAKS_SHA256} $RUNNER_TEMP/gitleaks.tar.gz" | sha256sum -c -
48
+ tar -xzf "$RUNNER_TEMP/gitleaks.tar.gz" -C "$RUNNER_TEMP" gitleaks
49
+ # A branch born with this push has no "before": the whole history is scanned.
50
+ case "$RANGE" in 0000000000000000000000000000000000000000..*) RANGE="" ;; esac
51
+ "$RUNNER_TEMP/gitleaks" git --redact --no-banner ${RANGE:+--log-opts="$RANGE"} .
41
52
  - uses: semgrep/semgrep-action@v1 # dangerous patterns (SAST)
42
53
  with: { config: "p/typescript p/owasp-top-ten" }
43
54
  - run: pnpm audit --audit-level=high # dependency CVEs
55
+
56
+ # Hosted mode (spec ogygie-hosted-mode B5): a pull request whose branch is
57
+ # spec/<id> is mergeable only once its spec is SIGNED (status validated).
58
+ # Require this check in the branch protection of the default branch: GitHub
59
+ # then holds the rule, never a UI. Any other branch passes and says so.
60
+ spec-signed:
61
+ if: "${{ github.event_name == 'pull_request' }}"
62
+ runs-on: ubuntu-latest
63
+ steps:
64
+ - uses: actions/checkout@v4
65
+ - uses: pnpm/action-setup@v4
66
+ - uses: actions/setup-node@v4
67
+ with: { node-version: 22, cache: pnpm }
68
+ - run: pnpm install --frozen-lockfile
69
+ - run: pnpm exec ogygie verify-spec-signed # reads GITHUB_HEAD_REF
70
+
71
+ # Hosted mode (spec ogygie-hosted-mode B4): on a push to spec/<id>, the
72
+ # generator derives on the branch what the spec declares, deterministically,
73
+ # and commits it there — never on the default branch — with the preview pages
74
+ # of a screen. The first schemas.ts of a NEW feature is drawn by the model and
75
+ # judged by the gates, written once; refused twice, the pull request gets the
76
+ # needs-operator label and nothing is generated. The holes are filled — library, draw, escalation — with the client's model key
77
+ # (a repo secret), the test of a candidate running without it; the body of a
78
+ # custom view is one of them, and gets the same label when no candidate is green. A repo
79
+ # without the generator installed is told so, and the branch is left as is.
80
+ # With OGYGIE_CI_TOKEN (a token allowed to push to spec/* branches), the
81
+ # commit it pushes gets its own checks; with the default token, it does not.
82
+ speculate:
83
+ if: "${{ github.event_name == 'push' && startsWith(github.ref_name, 'spec/') }}"
84
+ runs-on: ubuntu-latest
85
+ permissions:
86
+ contents: write
87
+ # The needs-operator label on the branch's pull request (labels go through the issues API).
88
+ pull-requests: write
89
+ issues: write
90
+ steps:
91
+ - uses: actions/checkout@v4
92
+ with:
93
+ fetch-depth: 0
94
+ token: "${{ secrets.OGYGIE_CI_TOKEN || github.token }}"
95
+ - uses: pnpm/action-setup@v4
96
+ - uses: actions/setup-node@v4
97
+ with: { node-version: 22, cache: pnpm }
98
+ - run: pnpm install --frozen-lockfile
99
+ - id: generator
100
+ run: |
101
+ if node -e "require.resolve('@ogygie/generate/cli')" 2>/dev/null; then
102
+ echo "installed=true" >> "$GITHUB_OUTPUT"
103
+ else
104
+ echo "installed=false" >> "$GITHUB_OUTPUT"
105
+ echo "ogygie-gates — @ogygie/generate n'est pas installé : la branche n'est pas spéculée."
106
+ fi
107
+ - if: "${{ steps.generator.outputs.installed == 'true' }}"
108
+ env:
109
+ OGYGIE_MODEL_API_KEY: "${{ secrets.OGYGIE_MODEL_API_KEY }}"
110
+ GH_TOKEN: "${{ github.token }}"
111
+ run: |
112
+ SPEC_ID="${GITHUB_REF_NAME#spec/}"
113
+ SPEC_FILE="specs/${SPEC_ID}.spec.md"
114
+ if [ ! -f "$SPEC_FILE" ]; then
115
+ echo "ogygie-gates — $SPEC_FILE absent de la branche : rien à spéculer."
116
+ exit 0
117
+ fi
118
+ if ! grep -q '^# Génération' "$SPEC_FILE"; then
119
+ echo "ogygie-gates — $SPEC_FILE sans section # Génération : rien à générer."
120
+ exit 0
121
+ fi
122
+ # A NEW feature has no schemas.ts yet: the model writes the first one, the
123
+ # gates judge it, two attempts (exit 2 = the operator). The pull request of
124
+ # the branch carries the verdict as a label, so the studio can say it.
125
+ pnpm exec ogygie generate schemas "$SPEC_ID" || SCHEMAS_STATUS=$?
126
+ PR_NUMBER="$(gh pr list --head "$GITHUB_REF_NAME" --state open --json number --jq '.[0].number' 2>/dev/null || true)"
127
+ if [ "${SCHEMAS_STATUS:-0}" = "2" ]; then
128
+ if [ -n "$PR_NUMBER" ]; then
129
+ gh label create needs-operator --force --color D9A35A --description "La spec attend un geste de l'opérateur : un schemas.ts refusé deux fois, ou le corps d'une vue custom sans candidat vert." >/dev/null
130
+ gh pr edit "$PR_NUMBER" --add-label needs-operator
131
+ fi
132
+ echo "ogygie-gates — le schemas.ts de $SPEC_ID attend l'opérateur (needs-operator) : rien n'est généré sur la branche."
133
+ exit 1
134
+ elif [ "${SCHEMAS_STATUS:-0}" != "0" ]; then
135
+ exit "$SCHEMAS_STATUS"
136
+ fi
137
+ if [ -n "$PR_NUMBER" ]; then
138
+ gh pr edit "$PR_NUMBER" --remove-label needs-operator 2>/dev/null || true
139
+ fi
140
+ pnpm exec ogygie generate check "$SPEC_ID"
141
+ pnpm exec ogygie generate generate "$SPEC_ID"
142
+ # The holes: the library first, then the client's model workspace key —
143
+ # a repo secret, present in THIS step alone; the test of a candidate
144
+ # runs in a process that holds no secret. Absent, the library alone.
145
+ pnpm exec ogygie generate fill "$SPEC_ID" || FILL_STATUS=$?
146
+ pnpm exec ogygie generate verify
147
+ if [ "${FILL_STATUS:-0}" = "2" ]; then
148
+ # The body of a custom view (hosted mode, the body as a hole): no candidate
149
+ # rendered its example — the operator writes it. The label says so; the
150
+ # view's own test stays red until then, so the pull request does too.
151
+ if [ -n "$PR_NUMBER" ]; then
152
+ gh label create needs-operator --force --color D9A35A --description "La spec attend un geste de l'opérateur : un schemas.ts refusé deux fois, ou le corps d'une vue custom sans candidat vert." >/dev/null
153
+ gh pr edit "$PR_NUMBER" --add-label needs-operator
154
+ fi
155
+ echo "ogygie-gates — le corps d'une vue custom de $SPEC_ID attend l'opérateur (needs-operator) : son test d'exemple reste rouge."
156
+ elif [ "${FILL_STATUS:-0}" != "0" ]; then
157
+ echo "ogygie-gates — des trous restent à l'humain, ou le plafond du modèle est atteint : voir le journal ci-dessus."
158
+ fi
159
+ # The previews of a screen (spec ogygie-hosted-mode B10): a page per
160
+ # state under previews/<id>/, committed with the derived code; not a
161
+ # screen, or a jsdom front: nothing to render, said, exit 0.
162
+ pnpm exec ogygie generate preview "$SPEC_ID"
163
+ - if: "${{ steps.generator.outputs.installed == 'true' }}"
164
+ run: |
165
+ # A commit that is no longer the head of the branch — a composition the
166
+ # model proposed before its preferred one — keeps its previews alone,
167
+ # under the composition's folder; the derived code is the head's.
168
+ HEAD_SHA="$(git ls-remote origin "refs/heads/${GITHUB_REF_NAME}" | cut -f1)"
169
+ if [ -n "$HEAD_SHA" ] && [ "$HEAD_SHA" != "$GITHUB_SHA" ]; then
170
+ git add previews 2>/dev/null || true
171
+ MESSAGE="speculate: ${GITHUB_REF_NAME#spec/} — aperçus d'une composition proposée (${GITHUB_SHA:0:7})"
172
+ else
173
+ git add -A
174
+ MESSAGE="speculate: ${GITHUB_REF_NAME#spec/} — dérivé par ogygie generate sur la branche"
175
+ fi
176
+ if git diff --cached --quiet; then
177
+ echo "ogygie-gates — rien de nouveau à committer sur la branche."
178
+ exit 0
179
+ fi
180
+ git config user.name "ogygie-speculate"
181
+ git config user.email "ogygie-speculate@users.noreply.github.com"
182
+ git commit -m "$MESSAGE"
183
+ # The branch may have moved since this commit was checked out: replay on its head.
184
+ git reset --hard HEAD --quiet
185
+ git clean -fdq
186
+ git pull --rebase origin "$GITHUB_REF_NAME"
187
+ git push origin "HEAD:${GITHUB_REF_NAME}"
@@ -1,32 +1,112 @@
1
1
  {
2
- "$intent": "Placeholder design tokens of the starter. A product REWRITES these values when it opens (AGENTS.product.md); the structure stays. The design tool composes ONLY from these tokens — a missing color or spacing is an amendment of this file (explicit decision), never a hardcoded value (AGENTS.md §Front).",
2
+ "$intent": "Design tokens of the starter: the Ogygie charter, two themes (dark first, the identity; light, a complete working theme). A product REWRITES these values when it opens (AGENTS.product.md); the structure stays. A colour or a shadow is either ONE value for every theme or one value per theme id of `themes`, the first theme being the default. The design tool composes ONLY from these tokens: a missing colour, spacing or motion value is an amendment of this file (explicit decision), never a hardcoded value (AGENTS.md §Front). The socle `@ogygie/ui` only names tokens (surface, border, text, accent, danger, focus...): every name it uses must stay declared here.",
3
+ "themes": [
4
+ "dark",
5
+ "light"
6
+ ],
3
7
  "color": {
4
- "surface": "#f6f6f4",
5
- "surface-raised": "#ffffff",
6
- "border": "#e2e1dc",
7
- "text": "#22241f",
8
- "text-muted": "#5f6259",
9
- "accent": "#3b5b92",
10
- "accent-contrast": "#ffffff",
11
- "positive": "#2f6b4f",
12
- "warning": "#a96a1f",
13
- "danger": "#b3423a",
14
- "focus": "#7aa2ff"
8
+ "surface": {
9
+ "dark": "#0b1322",
10
+ "light": "#f6f3ec"
11
+ },
12
+ "surface-raised": {
13
+ "dark": "#131d30",
14
+ "light": "#ffffff"
15
+ },
16
+ "surface-sunken": {
17
+ "dark": "#060b15",
18
+ "light": "#ece8de"
19
+ },
20
+ "border": {
21
+ "dark": "#223049",
22
+ "light": "#ddd7ca"
23
+ },
24
+ "border-strong": {
25
+ "dark": "#5a6d93",
26
+ "light": "#8b8571"
27
+ },
28
+ "text": {
29
+ "dark": "#ebe6da",
30
+ "light": "#121a2a"
31
+ },
32
+ "text-muted": {
33
+ "dark": "#a2abba",
34
+ "light": "#5a6475"
35
+ },
36
+ "text-faint": {
37
+ "dark": "#7b8694",
38
+ "light": "#667080"
39
+ },
40
+ "accent": {
41
+ "dark": "#5cd6cc",
42
+ "light": "#0f7580"
43
+ },
44
+ "accent-strong": {
45
+ "dark": "#86e6de",
46
+ "light": "#0a5c66"
47
+ },
48
+ "accent-soft": {
49
+ "dark": "#0e2b31",
50
+ "light": "#dbf2f3"
51
+ },
52
+ "accent-contrast": {
53
+ "dark": "#0b1322",
54
+ "light": "#f3fbfb"
55
+ },
56
+ "accent-glow": {
57
+ "dark": "#5cd6cc29",
58
+ "light": "#0f758024"
59
+ },
60
+ "orichalcum": {
61
+ "dark": "#d9a35a",
62
+ "light": "#9e5420"
63
+ },
64
+ "positive": {
65
+ "dark": "#8fd67a",
66
+ "light": "#2e7a1f"
67
+ },
68
+ "positive-soft": {
69
+ "dark": "#152a16",
70
+ "light": "#e2f3dc"
71
+ },
72
+ "warning": {
73
+ "dark": "#f2d06b",
74
+ "light": "#8a6400"
75
+ },
76
+ "warning-soft": {
77
+ "dark": "#2e2410",
78
+ "light": "#fbeccc"
79
+ },
80
+ "danger": {
81
+ "dark": "#ff8a7a",
82
+ "light": "#b42318"
83
+ },
84
+ "danger-soft": {
85
+ "dark": "#3a1612",
86
+ "light": "#fbe3df"
87
+ },
88
+ "focus": {
89
+ "dark": "#5cd6cc",
90
+ "light": "#0f7580"
91
+ }
15
92
  },
16
93
  "typography": {
17
- "font-sans": "system-ui, -apple-system, 'Segoe UI', sans-serif",
18
- "font-mono": "ui-monospace, 'SF Mono', 'Cascadia Mono', monospace",
94
+ "font-display": "'Cormorant Garamond', 'Iowan Old Style', Georgia, serif",
95
+ "font-sans": "'Hanken Grotesk', 'Helvetica Neue', Arial, sans-serif",
96
+ "font-mono": "'Fragment Mono', ui-monospace, 'SF Mono', Menlo, monospace",
19
97
  "size": {
20
98
  "xs": "0.75rem",
21
- "sm": "0.875rem",
22
- "base": "1rem",
99
+ "sm": "0.8125rem",
100
+ "base": "0.9375rem",
23
101
  "lg": "1.125rem",
24
- "xl": "1.375rem",
25
- "2xl": "1.75rem"
102
+ "xl": "1.25rem",
103
+ "2xl": "1.75rem",
104
+ "3xl": "3rem",
105
+ "4xl": "4.5rem"
26
106
  },
27
107
  "leading": {
28
- "tight": "1.25",
29
- "normal": "1.5"
108
+ "tight": "1.2",
109
+ "normal": "1.6"
30
110
  }
31
111
  },
32
112
  "spacing": {
@@ -36,15 +116,42 @@
36
116
  "4": "1rem",
37
117
  "6": "1.5rem",
38
118
  "8": "2rem",
39
- "12": "3rem"
119
+ "12": "3rem",
120
+ "16": "4rem",
121
+ "24": "6rem"
40
122
  },
41
123
  "radius": {
124
+ "xs": "0.125rem",
42
125
  "sm": "0.25rem",
43
126
  "md": "0.5rem",
44
127
  "lg": "0.75rem",
128
+ "xl": "1.25rem",
45
129
  "full": "9999px"
46
130
  },
47
131
  "shadow": {
48
- "raised": "0 1px 2px rgb(0 0 0 / 0.06), 0 2px 8px rgb(0 0 0 / 0.04)"
132
+ "raised": {
133
+ "dark": "inset 0 1px 0 rgb(255 255 255 / 0.05), 0 8px 24px rgb(0 0 0 / 0.4)",
134
+ "light": "0 1px 2px rgb(18 26 42 / 0.08), 0 8px 24px rgb(18 26 42 / 0.08)"
135
+ },
136
+ "overlay": {
137
+ "dark": "inset 0 1px 0 rgb(255 255 255 / 0.08), 0 24px 64px rgb(0 0 0 / 0.65)",
138
+ "light": "0 2px 4px rgb(18 26 42 / 0.08), 0 24px 64px rgb(18 26 42 / 0.16)"
139
+ },
140
+ "glow": {
141
+ "dark": "0 0 0 1px rgb(92 214 204 / 0.4), 0 0 48px rgb(92 214 204 / 0.2)",
142
+ "light": "0 0 0 1px rgb(15 117 128 / 0.4), 0 0 48px rgb(15 117 128 / 0.14)"
143
+ }
144
+ },
145
+ "motion": {
146
+ "ease": {
147
+ "swell": "cubic-bezier(0.2, 0.7, 0.2, 1)",
148
+ "retreat": "cubic-bezier(0.4, 0, 0.2, 1)"
149
+ },
150
+ "duration": {
151
+ "fast": "160ms",
152
+ "base": "320ms",
153
+ "slow": "640ms",
154
+ "count": "800ms"
155
+ }
49
156
  }
50
157
  }
@@ -23,6 +23,7 @@
23
23
  },
24
24
  "devDependencies": {
25
25
  "@ogygie/cli": "__OGYGIE_VERSION__",
26
+ "@ogygie/generate": "__OGYGIE_VERSION__",
26
27
  "@types/node": "^24.0.0",
27
28
  "tsx": "^4.20.0",
28
29
  "typescript": "^5.8.0",