@mriekki/lean 0.0.0-stage → 0.1.1
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 +66 -2
- package/assets/components.json +33 -0
- package/assets/core/distribution.json +51 -0
- package/assets/deployment/distribution.json +34 -0
- package/assets/generated/ASSEMBLY.json +382 -0
- package/assets/generated/core/core/agents/coach.agent.md +69 -0
- package/assets/generated/core/core/agents/reviewer.agent.md +85 -0
- package/assets/generated/core/core/instructions/lean-docs.instructions.md +14 -0
- package/assets/generated/core/core/instructions/lean-workflow.instructions.md +35 -0
- package/assets/generated/core/core/learning/CODING_DRILLS.md +35 -0
- package/assets/generated/core/core/learning/DIAGRAM_GUIDE.md +19 -0
- package/assets/generated/core/core/learning/ENGINEERING_LEARNING_WORKBOOK.md +82 -0
- package/assets/generated/core/core/learning/LEARNING_MAP.md +51 -0
- package/assets/generated/core/core/learning/LEARNING_PROGRESS.md +31 -0
- package/assets/generated/core/core/learning/RESOURCE_CATALOG.md +25 -0
- package/assets/generated/core/core/learning/SQL_DRILLS.md +27 -0
- package/assets/generated/core/core/learning/SYSTEM_DESIGN_DRILLS.md +21 -0
- package/assets/generated/core/core/process/COACHING_GUIDE.md +57 -0
- package/assets/generated/core/core/process/GUIDED_NEXT_STEPS.md +48 -0
- package/assets/generated/core/core/process/HUMAN_GATES.md +81 -0
- package/assets/generated/core/core/process/INTERACTIVE_SESSION_GUIDE.md +62 -0
- package/assets/generated/core/core/process/PRODUCTION_ENABLEMENT.md +59 -0
- package/assets/generated/core/core/process/PR_WORKFLOW.md +53 -0
- package/assets/generated/core/core/process/SETUP_ACCEPTANCE_CHECKLIST.md +72 -0
- package/assets/generated/core/core/process/WORK_ITEM_MODEL.md +56 -0
- package/assets/generated/core/core/skills/cleanup-branches/SKILL.md +190 -0
- package/assets/generated/core/core/skills/coach/SKILL.md +136 -0
- package/assets/generated/core/core/skills/code-walkthrough/SKILL.md +197 -0
- package/assets/generated/core/core/skills/core-sync/SKILL.md +220 -0
- package/assets/generated/core/core/skills/define-feature/SKILL.md +144 -0
- package/assets/generated/core/core/skills/deliver/SKILL.md +315 -0
- package/assets/generated/core/core/skills/deploy/SKILL.md +193 -0
- package/assets/generated/core/core/skills/grill-me/SKILL.md +77 -0
- package/assets/generated/core/core/skills/grill-with-docs/SKILL.md +90 -0
- package/assets/generated/core/core/skills/learning-review/SKILL.md +90 -0
- package/assets/generated/core/core/skills/next-step/SKILL.md +166 -0
- package/assets/generated/core/core/skills/production-readiness/SKILL.md +357 -0
- package/assets/generated/core/core/skills/pull-latest/SKILL.md +166 -0
- package/assets/generated/core/core/skills/refine-requirements/SKILL.md +207 -0
- package/assets/generated/core/core/skills/start-work/SKILL.md +178 -0
- package/assets/generated/core/core/skills/tear-down/SKILL.md +138 -0
- package/assets/generated/core/core/skills/upstream-fix/SKILL.md +206 -0
- package/assets/generated/core/core/skills/wait-what/SKILL.md +86 -0
- package/assets/generated/core/core/templates/FEATURE_BRIEF_TEMPLATE.md +27 -0
- package/assets/generated/core/core/templates/PRODUCT_BRIEF_TEMPLATE.md +27 -0
- package/assets/generated/core/core/templates/PULL_REQUEST_TEMPLATE.md +37 -0
- package/assets/generated/core/core/templates/REQUIREMENTS_TEMPLATE.md +17 -0
- package/assets/generated/core/profiles/greenfield/lean-workspace.json +12 -0
- package/assets/generated/core/tools/package/check_baseline.py +102 -0
- package/assets/generated/core/tools/package/check_upstream.py +171 -0
- package/assets/generated/deployment/distribution/lean-poc-deploy.py +8 -0
- package/assets/generated/deployment/src/lean_poc_deployment/__init__.py +3 -0
- package/assets/generated/deployment/src/lean_poc_deployment/__main__.py +3 -0
- package/assets/generated/deployment/src/lean_poc_deployment/app_cli.py +140 -0
- package/assets/generated/deployment/src/lean_poc_deployment/app_runtime.py +455 -0
- package/assets/generated/deployment/src/lean_poc_deployment/app_runtime_remote.py +416 -0
- package/assets/generated/deployment/src/lean_poc_deployment/box_platform.py +208 -0
- package/assets/generated/deployment/src/lean_poc_deployment/box_platform_assets.py +100 -0
- package/assets/generated/deployment/src/lean_poc_deployment/box_platform_remote.py +271 -0
- package/assets/generated/deployment/src/lean_poc_deployment/cli.py +437 -0
- package/assets/generated/deployment/src/lean_poc_deployment/doctor.py +121 -0
- package/assets/generated/deployment/src/lean_poc_deployment/entrypoint.py +25 -0
- package/assets/generated/deployment/src/lean_poc_deployment/errors.py +5 -0
- package/assets/generated/deployment/src/lean_poc_deployment/keys.py +171 -0
- package/assets/generated/deployment/src/lean_poc_deployment/profiles.py +204 -0
- package/assets/generated/deployment/src/lean_poc_deployment/route_registry.py +367 -0
- package/assets/generated/deployment/src/lean_poc_deployment/route_registry_remote.py +443 -0
- package/assets/generated/deployment/src/lean_poc_deployment/security.py +290 -0
- package/assets/generated/deployment/src/lean_poc_deployment/sqlite_adapter.py +335 -0
- package/assets/generated/deployment/src/lean_poc_deployment/sqlite_adapter_remote.py +340 -0
- package/assets/generated/deployment/src/lean_poc_deployment/sqlite_cli.py +86 -0
- package/assets/generated/deployment/src/lean_poc_deployment/sqlite_import_recovery.py +437 -0
- package/assets/generated/deployment/src/lean_poc_deployment/ssh.py +118 -0
- package/assets/generated/deployment/src/lean_poc_deployment/teardown.py +286 -0
- package/assets/generated/deployment/src/lean_poc_deployment/teardown_cli.py +120 -0
- package/assets/generated/deployment/src/lean_poc_deployment/teardown_remote.py +100 -0
- package/assets/generated/deployment/src/lean_poc_deployment/trust.py +278 -0
- package/bin/lean.mjs +9 -0
- package/package.json +31 -4
- package/src/cli.mjs +131 -0
- package/src/engine.mjs +243 -0
- package/src/package-data.mjs +18 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: upstream-fix
|
|
3
|
+
description: Route reusable bugs discovered in a consumer to the canonical shared repository, allow bounded upstream implementation, and return adoption to exact baseline sync.
|
|
4
|
+
argument-hint: "[optional problem summary]"
|
|
5
|
+
user-invocable: true
|
|
6
|
+
disable-model-invocation: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Upstream Fix
|
|
10
|
+
|
|
11
|
+
`/upstream-fix` is for a problem discovered while working in a consumer repository when the likely owner is shared Lean code rather than consumer product code.
|
|
12
|
+
|
|
13
|
+
The governing rule is:
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
discover in the consumer
|
|
17
|
+
→ diagnose/prove in the consumer
|
|
18
|
+
→ fix in the owning canonical repository
|
|
19
|
+
→ deliver there
|
|
20
|
+
→ sync the accepted shared commit back into the consumer
|
|
21
|
+
→ verify again in the consumer
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
A temporary edit to a shared-managed file inside the consumer may be useful as diagnosis/proof, but it is never the canonical delivered fix.
|
|
25
|
+
|
|
26
|
+
## 1. Invocation authorization
|
|
27
|
+
|
|
28
|
+
Invoking `/upstream-fix` authorizes:
|
|
29
|
+
|
|
30
|
+
- read-only ownership/provenance inspection in the consumer;
|
|
31
|
+
- classification of the problem to the owning layer;
|
|
32
|
+
- inspection of already-available local canonical repository candidates;
|
|
33
|
+
- when ownership and the intended patch are clear and a canonical checkout is safe, routine creation/use of one short-lived canonical work branch and application of only the bounded equivalent fix plus its focused tests/docs.
|
|
34
|
+
|
|
35
|
+
Invocation does **not** authorize:
|
|
36
|
+
|
|
37
|
+
- commit, push, PR creation, merge, release publication, or deployment;
|
|
38
|
+
- rewriting/discarding consumer work;
|
|
39
|
+
- changing more than one canonical repository in the same bounded fix;
|
|
40
|
+
- broad filesystem scans or cloning shared repositories merely to satisfy the helper;
|
|
41
|
+
- treating a temporary consumer patch as accepted shared implementation.
|
|
42
|
+
|
|
43
|
+
Normal review and `/deliver` occur later while focused on the owning canonical repository.
|
|
44
|
+
|
|
45
|
+
## 2. Establish consumer provenance
|
|
46
|
+
|
|
47
|
+
From the consumer root inspect, when present:
|
|
48
|
+
|
|
49
|
+
- `CORE_BASELINE.json`;
|
|
50
|
+
- `DEPLOYMENT_BASELINE.json`;
|
|
51
|
+
- `.github/lean-workspace.json`;
|
|
52
|
+
- current Git branch/status/diff;
|
|
53
|
+
- the files involved in the reported problem or temporary proof.
|
|
54
|
+
|
|
55
|
+
Run repository-local managed-baseline checks when available:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
python tools/lean-core/check-core-baseline.py --root .
|
|
59
|
+
python tools/lean-deployment/check-deployment-baseline.py
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Do not assume every generated-looking file is shared-owned. Use baseline managed-path records as the primary ownership evidence.
|
|
63
|
+
|
|
64
|
+
## 3. Classify the owner
|
|
65
|
+
|
|
66
|
+
Use the smallest owning layer that would need the same fix for another consumer.
|
|
67
|
+
|
|
68
|
+
### Consumer-owned
|
|
69
|
+
|
|
70
|
+
Examples: application domain logic, UI behavior, app-specific configuration, product tests, consumer architecture.
|
|
71
|
+
|
|
72
|
+
Result:
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
CONSUMER FIX
|
|
76
|
+
Owner: current repository
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Stop upstream routing and continue through the consumer's normal `/start-work` / implementation / review / `/deliver` flow.
|
|
80
|
+
|
|
81
|
+
### Core-owned
|
|
82
|
+
|
|
83
|
+
Evidence includes a changed/problematic path managed by `CORE_BASELINE.json`, or behavior defined by canonical Lean workflow skills/agents/instructions.
|
|
84
|
+
|
|
85
|
+
Result:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
CORE BUG
|
|
89
|
+
Owner: <repository recorded by CORE_BASELINE.json>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Deployment-Kit-owned
|
|
93
|
+
|
|
94
|
+
Evidence includes a changed/problematic path managed by `DEPLOYMENT_BASELINE.json`, or reusable deployment behavior owned by the pinned Deployment Kit.
|
|
95
|
+
|
|
96
|
+
Result:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
DEPLOYMENT BUG
|
|
100
|
+
Owner: <repository recorded by DEPLOYMENT_BASELINE.json>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Generator/control-plane owned
|
|
104
|
+
|
|
105
|
+
If generated output is wrong but the managed payload itself is correct, classify the generator/orchestrator rather than patching the generated consumer. Resolve ownership from repository/project evidence; do not guess between Bootstrap and a control plane when evidence is insufficient.
|
|
106
|
+
|
|
107
|
+
If ownership is genuinely ambiguous, stop at `UPSTREAM OWNER UNCLEAR` and present the competing evidence. Do not write to multiple repositories speculatively.
|
|
108
|
+
|
|
109
|
+
## 4. Temporary consumer proof
|
|
110
|
+
|
|
111
|
+
A temporary consumer edit to a shared-managed path is allowed for diagnosis when it helps prove the fix in the real application context.
|
|
112
|
+
|
|
113
|
+
Clearly report it as:
|
|
114
|
+
|
|
115
|
+
```text
|
|
116
|
+
TEMPORARY SHARED-MANAGED PROOF
|
|
117
|
+
Consumer path: <path>
|
|
118
|
+
Canonical owner: <repo>
|
|
119
|
+
Do not deliver this consumer copy.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Do not silently revert/discard the proof, because it may contain useful evidence. Do not stage/commit it as ordinary consumer implementation. Adoption later replaces the private proof with the accepted shared baseline.
|
|
123
|
+
|
|
124
|
+
## 5. Resolve a canonical local checkout
|
|
125
|
+
|
|
126
|
+
For the classified owner, consider local candidates in this order:
|
|
127
|
+
|
|
128
|
+
1. explicit path supplied by the human;
|
|
129
|
+
2. an already-open workspace folder whose Git remote unambiguously matches the recorded canonical repository;
|
|
130
|
+
3. a normal sibling checkout derived from the canonical repository name.
|
|
131
|
+
|
|
132
|
+
Do not broadly scan the filesystem. A multi-root maintainer workspace is a convenience, not a requirement.
|
|
133
|
+
|
|
134
|
+
A candidate is safe for bounded upstream implementation only when:
|
|
135
|
+
|
|
136
|
+
- it is the expected Git repository;
|
|
137
|
+
- Git state can be inspected;
|
|
138
|
+
- no merge/rebase/cherry-pick is in progress;
|
|
139
|
+
- unrelated local work is absent;
|
|
140
|
+
- branch state does not require reset/rebase/force/stash/discard operations.
|
|
141
|
+
|
|
142
|
+
If no safe local canonical checkout exists, report the exact canonical repository and the proven consumer evidence. Do not clone automatically just to continue.
|
|
143
|
+
|
|
144
|
+
## 6. Port the bounded fix upstream
|
|
145
|
+
|
|
146
|
+
When the safe local canonical checkout exists and the equivalent fix is clear:
|
|
147
|
+
|
|
148
|
+
1. inspect the canonical implementation and tests before editing;
|
|
149
|
+
2. create/use a repository-appropriate short-lived branch tied to the upstream issue/work item when needed;
|
|
150
|
+
3. reproduce the root cause against canonical code where practical;
|
|
151
|
+
4. apply the reusable fix in the canonical source, not by copying consumer-specific names/configuration;
|
|
152
|
+
5. add/update focused canonical tests that would have caught the defect;
|
|
153
|
+
6. update canonical docs only when the contract changed;
|
|
154
|
+
7. run relevant local checks when available;
|
|
155
|
+
8. show the canonical meaningful diff and observed validation.
|
|
156
|
+
|
|
157
|
+
Preserve consumer specificity. A shared fix must not hard-code the consumer name, target, data, environment, route, or product requirement merely because that consumer exposed the bug.
|
|
158
|
+
|
|
159
|
+
If the consumer proof cannot be generalized safely, stop and reclassify or ask for a narrower canonical design rather than forcing the patch upstream.
|
|
160
|
+
|
|
161
|
+
## 7. Handoff to canonical delivery
|
|
162
|
+
|
|
163
|
+
After the upstream implementation is ready, report:
|
|
164
|
+
|
|
165
|
+
```text
|
|
166
|
+
UPSTREAM FIX READY
|
|
167
|
+
|
|
168
|
+
Owner: <canonical repo>
|
|
169
|
+
Branch: <branch>
|
|
170
|
+
Consumer proof: <path/status>
|
|
171
|
+
Validation: <observed checks>
|
|
172
|
+
Next: focus the canonical repository and run /deliver
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`/upstream-fix` does not commit/push/open/merge the canonical PR itself. `/deliver` retains the normal reviewed-diff and merge/finalize gates in exactly one repository.
|
|
176
|
+
|
|
177
|
+
## 8. Adopt the accepted fix back into the consumer
|
|
178
|
+
|
|
179
|
+
After the canonical PR is merged and its accepted default-branch CI is green:
|
|
180
|
+
|
|
181
|
+
- Core-owned fixes return through `/core-sync` using the exact accepted Core commit/distribution;
|
|
182
|
+
- Deployment-Kit-owned fixes return through the canonical Deployment Kit consumer-sync capability and refresh `DEPLOYMENT_BASELINE.json`;
|
|
183
|
+
- generator/control-plane fixes are validated by regenerating or using that platform's canonical update path, not by manually editing generated managed files.
|
|
184
|
+
|
|
185
|
+
Then rerun the original consumer reproduction and baseline checks.
|
|
186
|
+
|
|
187
|
+
A successful consumer retest is evidence that the shared fix works in context; it does not turn the consumer copy into the canonical source.
|
|
188
|
+
|
|
189
|
+
## 9. Consumer delivery guard
|
|
190
|
+
|
|
191
|
+
If `/deliver` is invoked in a consumer with unexplained Core- or Deployment-Kit-managed file drift, stop before staging/commit/push. Route reusable managed-file changes through `/upstream-fix` and accepted baseline sync first.
|
|
192
|
+
|
|
193
|
+
Consumer-owned changes may be delivered normally when managed baselines are clean.
|
|
194
|
+
|
|
195
|
+
## 10. Boundaries
|
|
196
|
+
|
|
197
|
+
`/upstream-fix` never:
|
|
198
|
+
|
|
199
|
+
- merges changes;
|
|
200
|
+
- publishes releases/tags;
|
|
201
|
+
- deploys production;
|
|
202
|
+
- commits one change across multiple repositories;
|
|
203
|
+
- auto-stashes, resets, rebases, force-pushes, or discards work;
|
|
204
|
+
- turns a maintainer multi-root workspace into generated repository content;
|
|
205
|
+
- requires ordinary consumer users to clone shared repositories;
|
|
206
|
+
- treats a temporary shared-managed consumer edit as a legitimate permanent fork.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wait-what
|
|
3
|
+
description: "The last explanation did not land. Re-explain it with enough context to understand where we are and what happens next."
|
|
4
|
+
argument-hint: "[optional note about what was unclear]"
|
|
5
|
+
user-invocable: true
|
|
6
|
+
disable-model-invocation: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Wait, What?
|
|
10
|
+
|
|
11
|
+
Purpose: repair communication when the immediately preceding explanation did not land.
|
|
12
|
+
|
|
13
|
+
This skill re-explains. It does not perform new engineering work.
|
|
14
|
+
|
|
15
|
+
## 1. Use existing context first
|
|
16
|
+
|
|
17
|
+
Re-pitch the immediately preceding explanation, or the specific unclear point named by the human.
|
|
18
|
+
|
|
19
|
+
Prefer already-available conversation/repository context. Do not run commands, inspect new Git/GitHub state, modify files, or take the next workflow action merely to explain the prior response.
|
|
20
|
+
|
|
21
|
+
If a safe explanation depends on information that is genuinely missing, say what is missing instead of inventing it.
|
|
22
|
+
|
|
23
|
+
## 2. Use plain technical English
|
|
24
|
+
|
|
25
|
+
Prefer:
|
|
26
|
+
- short, direct sentences;
|
|
27
|
+
- concrete nouns instead of vague pronouns;
|
|
28
|
+
- one idea per sentence when possible;
|
|
29
|
+
- familiar repository/work-item terminology;
|
|
30
|
+
- explicit cause-and-effect language;
|
|
31
|
+
- examples only when they make the explanation clearer.
|
|
32
|
+
|
|
33
|
+
Use a Simplified-Technical-English style, but do not claim formal ASD-STE100 compliance.
|
|
34
|
+
|
|
35
|
+
When repository glossary material exists, prefer its terminology. A glossary is helpful, not required.
|
|
36
|
+
|
|
37
|
+
## 3. Re-establish orientation
|
|
38
|
+
|
|
39
|
+
For a substantive explanation, cover only what helps:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
CURRENT CONTEXT
|
|
43
|
+
Where we are and what led here.
|
|
44
|
+
|
|
45
|
+
WHAT I MEANT
|
|
46
|
+
The important point in simpler language.
|
|
47
|
+
|
|
48
|
+
WHY IT MATTERS
|
|
49
|
+
Why the point affects the current work or decision.
|
|
50
|
+
|
|
51
|
+
WHAT HAPPENS NEXT
|
|
52
|
+
The immediate next step, if there is one.
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Do not force all headings for a tiny clarification. Keep the response proportional to the confusion.
|
|
56
|
+
|
|
57
|
+
## 4. Respect human gates
|
|
58
|
+
|
|
59
|
+
If the unclear explanation immediately preceded a human approval or consequential-action gate, complete the re-explanation before asking for that approval again.
|
|
60
|
+
|
|
61
|
+
Do not treat `/wait-what` as approval to continue, write files, commit, push, merge, deploy, or call external systems.
|
|
62
|
+
|
|
63
|
+
## 5. Route deeper needs without changing modes silently
|
|
64
|
+
|
|
65
|
+
If the human actually needs:
|
|
66
|
+
- current workflow-state orientation, suggest `/next-step`;
|
|
67
|
+
- deeper concept teaching, suggest `/coach`;
|
|
68
|
+
- a structured explanation of implementation changes, suggest `/code-walkthrough`;
|
|
69
|
+
- an understanding check, suggest `/learning-review`.
|
|
70
|
+
|
|
71
|
+
Do not silently switch into those modes unless the human asks.
|
|
72
|
+
|
|
73
|
+
## 6. Boundaries
|
|
74
|
+
|
|
75
|
+
`/wait-what` is:
|
|
76
|
+
- read-only,
|
|
77
|
+
- user-invoked,
|
|
78
|
+
- non-blocking,
|
|
79
|
+
- communication-focused,
|
|
80
|
+
- usable during requirements, planning, implementation, review, delivery, setup, learning, or operations discussions.
|
|
81
|
+
|
|
82
|
+
It does not create a new engineering workflow state.
|
|
83
|
+
|
|
84
|
+
## Provenance
|
|
85
|
+
|
|
86
|
+
Inspired by Matt Pocock's `wait-what` productivity skill in `mattpocock/skills` (MIT licensed). This Lean Core version is independently rewritten for repository-agnostic Human-in-the-Loop engineering workflows.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Feature Brief — <Name>
|
|
2
|
+
|
|
3
|
+
> Authority: <AUTHORITATIVE | SUPPLEMENTAL / EXPERIMENTAL>
|
|
4
|
+
>
|
|
5
|
+
> Jira relationship: <Epic / Story set / other>
|
|
6
|
+
>
|
|
7
|
+
> A Jira Epic is not automatically equivalent to this Feature Brief.
|
|
8
|
+
|
|
9
|
+
## Problem / User Outcome
|
|
10
|
+
|
|
11
|
+
## Scope
|
|
12
|
+
|
|
13
|
+
## Non-Scope
|
|
14
|
+
|
|
15
|
+
## Workflows
|
|
16
|
+
|
|
17
|
+
## Cross-Story / Domain Rules
|
|
18
|
+
|
|
19
|
+
## Dependencies
|
|
20
|
+
|
|
21
|
+
## Related Jira Epics / Stories
|
|
22
|
+
|
|
23
|
+
## Acceptance Summary
|
|
24
|
+
|
|
25
|
+
## Open Decisions
|
|
26
|
+
|
|
27
|
+
## Approval / Status
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Product Brief — <Name>
|
|
2
|
+
|
|
3
|
+
> Authority: <AUTHORITATIVE | SUPPLEMENTAL / EXPERIMENTAL>
|
|
4
|
+
>
|
|
5
|
+
> External system relationship: <for example Jira Epics/Stories remain primary>
|
|
6
|
+
|
|
7
|
+
## Problem / Opportunity
|
|
8
|
+
|
|
9
|
+
## Users / Stakeholders
|
|
10
|
+
|
|
11
|
+
## Desired Outcomes
|
|
12
|
+
|
|
13
|
+
## Scope
|
|
14
|
+
|
|
15
|
+
## Non-Scope
|
|
16
|
+
|
|
17
|
+
## Major Workflows / Capabilities
|
|
18
|
+
|
|
19
|
+
## Domain / Policy Constraints
|
|
20
|
+
|
|
21
|
+
## Success Measures
|
|
22
|
+
|
|
23
|
+
## Related Jira Epics / Work Items
|
|
24
|
+
|
|
25
|
+
## Open Decisions
|
|
26
|
+
|
|
27
|
+
## Approval / Status
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Internal PR-body scaffolding for the Lean delivery workflow.
|
|
3
|
+
Populate from actual work-item, diff, test, review, and CI evidence.
|
|
4
|
+
Do not mark checks complete unless the evidence was observed.
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
## Work item
|
|
8
|
+
|
|
9
|
+
<!-- ID and concise title -->
|
|
10
|
+
|
|
11
|
+
## What changed
|
|
12
|
+
|
|
13
|
+
-
|
|
14
|
+
-
|
|
15
|
+
-
|
|
16
|
+
|
|
17
|
+
## Acceptance evidence
|
|
18
|
+
|
|
19
|
+
- [ ] Relevant local formatting/build/test/checks pass
|
|
20
|
+
- [ ] Relevant runtime/browser/container checks pass when applicable
|
|
21
|
+
- [ ] GitHub Actions passes
|
|
22
|
+
|
|
23
|
+
Mark items N/A in notes when they do not apply.
|
|
24
|
+
|
|
25
|
+
## Review
|
|
26
|
+
|
|
27
|
+
- [ ] Lean Reviewer completed when required by the work classification
|
|
28
|
+
- [ ] Blocking findings resolved
|
|
29
|
+
- [ ] Human final PR diff review completed
|
|
30
|
+
|
|
31
|
+
## Known limitations / follow-up
|
|
32
|
+
|
|
33
|
+
-
|
|
34
|
+
|
|
35
|
+
## Notes
|
|
36
|
+
|
|
37
|
+
<!-- Keep concise. Link to authoritative work-item docs instead of duplicating requirements. -->
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Detailed Requirements — <Work Item>
|
|
2
|
+
|
|
3
|
+
> Use only when local detailed requirements are permitted and useful.
|
|
4
|
+
>
|
|
5
|
+
> Do not duplicate Jira/Issue authority without an explicit reason.
|
|
6
|
+
|
|
7
|
+
## User / Engineering Outcomes
|
|
8
|
+
|
|
9
|
+
## Domain / Engineering Requirements
|
|
10
|
+
|
|
11
|
+
## Acceptance / Validation Scenarios
|
|
12
|
+
|
|
13
|
+
## Open Decisions
|
|
14
|
+
|
|
15
|
+
## Traceability
|
|
16
|
+
|
|
17
|
+
## Authority / Approval
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"profile": "greenfield",
|
|
4
|
+
"workItemSource": "local-docs",
|
|
5
|
+
"productBriefs": {"mode": "authoritative"},
|
|
6
|
+
"featureBriefs": {"mode": "authoritative"},
|
|
7
|
+
"engineeringEnablers": {"mode": "authoritative"},
|
|
8
|
+
"localDetailedRequirements": {"mode": "authoritative-combined"},
|
|
9
|
+
"jira": {"accessMode": "none", "epics": "not-used"},
|
|
10
|
+
"delivery": {"pr": true, "explicitFinalPrAcceptance": true, "explicitMerge": true, "postMergeCi": "observe-if-configured"},
|
|
11
|
+
"productionReadiness": {"enabled": "when-production-in-scope"}
|
|
12
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
BASELINE_FILENAME = "CORE_BASELINE.json"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def sha256_file(path: Path) -> str:
|
|
13
|
+
digest = hashlib.sha256()
|
|
14
|
+
with path.open("rb") as handle:
|
|
15
|
+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
16
|
+
digest.update(chunk)
|
|
17
|
+
return digest.hexdigest()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def resolve_managed_path(root: Path, rel: str) -> Path:
|
|
21
|
+
candidate = Path(rel)
|
|
22
|
+
if candidate.is_absolute() or ".." in candidate.parts:
|
|
23
|
+
raise ValueError(f"unsafe managed path: {rel}")
|
|
24
|
+
resolved = (root / candidate).resolve()
|
|
25
|
+
try:
|
|
26
|
+
resolved.relative_to(root)
|
|
27
|
+
except ValueError as exc:
|
|
28
|
+
raise ValueError(f"managed path escapes repository root: {rel}") from exc
|
|
29
|
+
return resolved
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def verify(root: Path, baseline_path: Path | None = None) -> int:
|
|
33
|
+
root = root.resolve()
|
|
34
|
+
baseline_path = baseline_path.resolve() if baseline_path else root / BASELINE_FILENAME
|
|
35
|
+
if not baseline_path.is_file():
|
|
36
|
+
print(f"MISSING baseline: {baseline_path}")
|
|
37
|
+
return 2
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
baseline = json.loads(baseline_path.read_text(encoding="utf-8"))
|
|
41
|
+
except json.JSONDecodeError as exc:
|
|
42
|
+
print(f"INVALID baseline JSON: {exc}")
|
|
43
|
+
return 2
|
|
44
|
+
|
|
45
|
+
if baseline.get("schemaVersion") != 1:
|
|
46
|
+
print(f"UNSUPPORTED baseline schemaVersion: {baseline.get('schemaVersion')}")
|
|
47
|
+
return 2
|
|
48
|
+
|
|
49
|
+
managed = baseline.get("managedPaths")
|
|
50
|
+
if not isinstance(managed, list) or not managed:
|
|
51
|
+
print("INVALID baseline: managedPaths is missing or empty")
|
|
52
|
+
return 2
|
|
53
|
+
|
|
54
|
+
failures = 0
|
|
55
|
+
seen: set[str] = set()
|
|
56
|
+
for item in managed:
|
|
57
|
+
rel = item.get("path")
|
|
58
|
+
expected = item.get("sha256")
|
|
59
|
+
if not rel or not expected:
|
|
60
|
+
print(f"INVALID managed path entry: {item!r}")
|
|
61
|
+
failures += 1
|
|
62
|
+
continue
|
|
63
|
+
if rel in seen:
|
|
64
|
+
print(f"INVALID duplicate managed path: {rel}")
|
|
65
|
+
failures += 1
|
|
66
|
+
continue
|
|
67
|
+
seen.add(rel)
|
|
68
|
+
try:
|
|
69
|
+
path = resolve_managed_path(root, rel)
|
|
70
|
+
except ValueError as exc:
|
|
71
|
+
print(f"INVALID {exc}")
|
|
72
|
+
failures += 1
|
|
73
|
+
continue
|
|
74
|
+
if not path.is_file():
|
|
75
|
+
print(f"MISSING {rel}")
|
|
76
|
+
failures += 1
|
|
77
|
+
continue
|
|
78
|
+
actual = sha256_file(path)
|
|
79
|
+
if actual != expected:
|
|
80
|
+
print(f"DRIFT {rel}")
|
|
81
|
+
failures += 1
|
|
82
|
+
else:
|
|
83
|
+
print(f"SAME {rel}")
|
|
84
|
+
|
|
85
|
+
print()
|
|
86
|
+
if failures:
|
|
87
|
+
print(f"Core baseline integrity: FAIL ({failures} path(s))")
|
|
88
|
+
return 1
|
|
89
|
+
print(f"Core baseline integrity: PASS ({len(managed)} managed path(s))")
|
|
90
|
+
return 0
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def main() -> int:
|
|
94
|
+
parser = argparse.ArgumentParser(description="Verify a consumer's Core-managed paths against CORE_BASELINE.json.")
|
|
95
|
+
parser.add_argument("--root", type=Path, default=Path.cwd(), help="Consumer repository root")
|
|
96
|
+
parser.add_argument("--baseline", type=Path, help="Optional explicit baseline file")
|
|
97
|
+
args = parser.parse_args()
|
|
98
|
+
return verify(args.root, args.baseline)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
if __name__ == "__main__":
|
|
102
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
import urllib.error
|
|
9
|
+
import urllib.parse
|
|
10
|
+
import urllib.request
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Callable
|
|
13
|
+
|
|
14
|
+
BASELINE_FILENAME = "CORE_BASELINE.json"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UpstreamUnavailable(RuntimeError):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load_baseline(root: Path) -> dict:
|
|
22
|
+
path = root / BASELINE_FILENAME
|
|
23
|
+
if not path.is_file():
|
|
24
|
+
raise SystemExit(f"Missing {BASELINE_FILENAME}: {path}")
|
|
25
|
+
try:
|
|
26
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
27
|
+
except json.JSONDecodeError as exc:
|
|
28
|
+
raise SystemExit(f"Invalid {BASELINE_FILENAME}: {exc}") from exc
|
|
29
|
+
required = ["coreRepository", "coreCommit", "coreVersion", "distribution"]
|
|
30
|
+
missing = [key for key in required if not data.get(key)]
|
|
31
|
+
if missing:
|
|
32
|
+
raise SystemExit(f"{BASELINE_FILENAME} missing required field(s): {', '.join(missing)}")
|
|
33
|
+
return data
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def github_get_json(url: str, token: str | None) -> dict:
|
|
37
|
+
headers = {
|
|
38
|
+
"Accept": "application/vnd.github+json",
|
|
39
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
40
|
+
"User-Agent": "lean-agentic-core-upstream-check",
|
|
41
|
+
}
|
|
42
|
+
if token:
|
|
43
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
44
|
+
request = urllib.request.Request(url, headers=headers)
|
|
45
|
+
try:
|
|
46
|
+
with urllib.request.urlopen(request, timeout=20) as response:
|
|
47
|
+
return json.load(response)
|
|
48
|
+
except (urllib.error.HTTPError, urllib.error.URLError, TimeoutError) as exc:
|
|
49
|
+
raise UpstreamUnavailable(str(exc)) from exc
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def resolve_remote_head(repository: str, ref: str, token: str | None, fetch: Callable[[str, str | None], dict]) -> str:
|
|
53
|
+
quoted_ref = urllib.parse.quote(ref, safe="")
|
|
54
|
+
data = fetch(f"https://api.github.com/repos/{repository}/commits/{quoted_ref}", token)
|
|
55
|
+
sha = str(data.get("sha") or "").strip()
|
|
56
|
+
if not sha:
|
|
57
|
+
raise UpstreamUnavailable("GitHub response did not include a commit SHA")
|
|
58
|
+
return sha
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def compare_remote(
|
|
62
|
+
repository: str,
|
|
63
|
+
baseline_sha: str,
|
|
64
|
+
upstream_sha: str,
|
|
65
|
+
token: str | None,
|
|
66
|
+
fetch: Callable[[str, str | None], dict],
|
|
67
|
+
) -> dict:
|
|
68
|
+
return fetch(
|
|
69
|
+
f"https://api.github.com/repos/{repository}/compare/{baseline_sha}...{upstream_sha}",
|
|
70
|
+
token,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def resolve_local_head(core_path: Path, ref: str) -> str:
|
|
75
|
+
try:
|
|
76
|
+
result = subprocess.run(
|
|
77
|
+
["git", "-C", str(core_path), "rev-parse", ref],
|
|
78
|
+
check=True,
|
|
79
|
+
capture_output=True,
|
|
80
|
+
text=True,
|
|
81
|
+
)
|
|
82
|
+
except (OSError, subprocess.CalledProcessError) as exc:
|
|
83
|
+
raise UpstreamUnavailable(f"unable to resolve {ref!r} in {core_path}") from exc
|
|
84
|
+
return result.stdout.strip()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def check_upstream(
|
|
88
|
+
root: Path,
|
|
89
|
+
*,
|
|
90
|
+
ref: str = "main",
|
|
91
|
+
token: str | None = None,
|
|
92
|
+
core_path: Path | None = None,
|
|
93
|
+
skip_if_unavailable: bool = False,
|
|
94
|
+
fetch: Callable[[str, str | None], dict] = github_get_json,
|
|
95
|
+
) -> int:
|
|
96
|
+
root = root.resolve()
|
|
97
|
+
baseline = load_baseline(root)
|
|
98
|
+
repository = baseline["coreRepository"]
|
|
99
|
+
baseline_sha = baseline["coreCommit"]
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
if core_path is not None:
|
|
103
|
+
upstream_sha = resolve_local_head(core_path.resolve(), ref)
|
|
104
|
+
comparison = None
|
|
105
|
+
else:
|
|
106
|
+
upstream_sha = resolve_remote_head(repository, ref, token, fetch)
|
|
107
|
+
comparison = None if upstream_sha == baseline_sha else compare_remote(
|
|
108
|
+
repository, baseline_sha, upstream_sha, token, fetch
|
|
109
|
+
)
|
|
110
|
+
except UpstreamUnavailable as exc:
|
|
111
|
+
if skip_if_unavailable:
|
|
112
|
+
print(f"Core upstream freshness: SKIP ({exc})")
|
|
113
|
+
return 0
|
|
114
|
+
print(f"Core upstream freshness: UNAVAILABLE ({exc})")
|
|
115
|
+
return 2
|
|
116
|
+
|
|
117
|
+
print(f"Consumer baseline: {baseline['coreVersion']} @ {baseline_sha}")
|
|
118
|
+
print(f"Upstream {ref}: {upstream_sha}")
|
|
119
|
+
if upstream_sha == baseline_sha:
|
|
120
|
+
print("Core upstream freshness: PASS")
|
|
121
|
+
return 0
|
|
122
|
+
|
|
123
|
+
if comparison:
|
|
124
|
+
status = comparison.get("status", "unknown")
|
|
125
|
+
ahead = comparison.get("ahead_by")
|
|
126
|
+
behind = comparison.get("behind_by")
|
|
127
|
+
if status in {"ahead", "identical"} and isinstance(ahead, int):
|
|
128
|
+
print(f"Core upstream freshness: STALE ({ahead} upstream commit(s) ahead)")
|
|
129
|
+
else:
|
|
130
|
+
details = []
|
|
131
|
+
if status:
|
|
132
|
+
details.append(f"status={status}")
|
|
133
|
+
if isinstance(ahead, int):
|
|
134
|
+
details.append(f"ahead_by={ahead}")
|
|
135
|
+
if isinstance(behind, int):
|
|
136
|
+
details.append(f"behind_by={behind}")
|
|
137
|
+
print("Core upstream freshness: DIVERGED" + (f" ({', '.join(details)})" if details else ""))
|
|
138
|
+
else:
|
|
139
|
+
print("Core upstream freshness: STALE")
|
|
140
|
+
return 1
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def main() -> int:
|
|
144
|
+
parser = argparse.ArgumentParser(description="Check whether a consumer's recorded Lean Core commit is current upstream.")
|
|
145
|
+
parser.add_argument("--root", type=Path, default=Path.cwd(), help="Consumer repository root")
|
|
146
|
+
parser.add_argument("--ref", default="main", help="Core ref to compare against (default: main)")
|
|
147
|
+
parser.add_argument(
|
|
148
|
+
"--token-env",
|
|
149
|
+
default="LEAN_CORE_READ_TOKEN",
|
|
150
|
+
help="Environment variable containing a GitHub token for private Core access",
|
|
151
|
+
)
|
|
152
|
+
parser.add_argument("--core-path", type=Path, help="Use a local Core checkout instead of GitHub API")
|
|
153
|
+
parser.add_argument(
|
|
154
|
+
"--skip-if-unavailable",
|
|
155
|
+
action="store_true",
|
|
156
|
+
help="Exit successfully if upstream cannot be queried (useful when CI token is not configured)",
|
|
157
|
+
)
|
|
158
|
+
args = parser.parse_args()
|
|
159
|
+
|
|
160
|
+
token = os.getenv(args.token_env) or os.getenv("GITHUB_TOKEN")
|
|
161
|
+
return check_upstream(
|
|
162
|
+
args.root,
|
|
163
|
+
ref=args.ref,
|
|
164
|
+
token=token,
|
|
165
|
+
core_path=args.core_path,
|
|
166
|
+
skip_if_unavailable=args.skip_if_unavailable,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
if __name__ == "__main__":
|
|
171
|
+
raise SystemExit(main())
|