@1aboveio/skills 1.0.10 → 1.0.12
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/.codex-plugin/plugin.json +1 -1
- package/.codex-plugin/resources.json +298 -216
- package/README.md +3 -3
- package/package.json +10 -8
- package/skills/app-dev/airflow/SKILL.md +1 -1
- package/skills/app-dev/android-foundation/SKILL.md +35 -37
- package/skills/app-dev/android-foundation/references/delivery-focus.md +4 -2
- package/skills/app-dev/android-foundation/references/feature-work.md +50 -0
- package/skills/app-dev/android-foundation/references/setup.md +24 -9
- package/skills/app-dev/android-foundation/references/workspace.md +81 -0
- package/skills/app-dev/android-foundation/scripts/workspace-runtime/bin/workspaces.mjs +52 -0
- package/skills/app-dev/android-foundation/scripts/workspace-runtime/native-workspace.mjs +239 -0
- package/skills/app-dev/android-foundation/scripts/workspace-runtime/workspace-lifecycle.mjs +2154 -0
- package/skills/app-dev/app-debug/SKILL.md +1 -1
- package/skills/app-dev/better-auth/SKILL.md +1 -1
- package/skills/app-dev/data-catalog/SKILL.md +1 -1
- package/skills/app-dev/data-quality/SKILL.md +1 -1
- package/skills/app-dev/ios-foundation/SKILL.md +15 -10
- package/skills/app-dev/ios-foundation/references/setup.md +25 -9
- package/skills/app-dev/ios-foundation/references/workspace.md +82 -0
- package/skills/app-dev/ios-foundation/scripts/workspace-runtime/bin/workspaces.mjs +52 -0
- package/skills/app-dev/ios-foundation/scripts/workspace-runtime/native-workspace.mjs +239 -0
- package/skills/app-dev/ios-foundation/scripts/workspace-runtime/workspace-lifecycle.mjs +2154 -0
- package/skills/app-dev/monorepo/SKILL.md +1 -1
- package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
- package/skills/app-dev/prisma-setup/SKILL.md +1 -1
- package/skills/app-dev/pyspark/SKILL.md +1 -1
- package/skills/app-dev/python-backend/SKILL.md +1 -1
- package/skills/app-dev/shadcn/SKILL.md +1 -1
- package/skills/app-dev/zod-v4/SKILL.md +1 -1
- package/skills/cicd/cloud-build/SKILL.md +1 -1
- package/skills/cicd/cloud-debug/SKILL.md +1 -1
- package/skills/cicd/cloud-deploy/SKILL.md +1 -1
- package/skills/cicd/docker/SKILL.md +1 -1
- package/skills/cicd/github-actions/SKILL.md +1 -1
- package/skills/cicd/github-flow/SKILL.md +1 -1
- package/skills/cicd/google-cloud/SKILL.md +1 -1
- package/skills/cicd/mergify/SKILL.md +1 -1
- package/skills/cicd/podman/SKILL.md +1 -1
- package/skills/content/article-review/SKILL.md +1 -1
- package/skills/content/article-to-image/SKILL.md +1 -1
- package/skills/content/flowchart-svg/SKILL.md +1 -1
- package/skills/content/image-gen/SKILL.md +1 -1
- package/skills/content/mermaid/SKILL.md +1 -1
- package/skills/content/wechat-article-writer/SKILL.md +1 -1
- package/skills/content/wechat-publisher/SKILL.md +1 -1
- package/skills/content/wechat-search/SKILL.md +1 -1
- package/skills/content/xai-search/SKILL.md +1 -1
- package/skills/core/distill-writing/SKILL.md +3 -3
- package/skills/core/distill-writing/references/extraction.md +1 -1
- package/skills/core/doc-to-md/SKILL.md +1 -1
- package/skills/core/editorial-review/SKILL.md +82 -12
- package/skills/core/editorial-review/references/checklist.md +26 -1
- package/skills/core/house-style/SKILL.md +8 -3
- package/skills/core/house-style/references/analytical-reporting.md +123 -0
- package/skills/core/house-style/references/analytical-writing-patterns.md +79 -0
- package/skills/core/house-style/references/analytical-writing-research.md +129 -0
- package/skills/core/{write-doc → house-style}/references/bilingual.md +7 -5
- package/skills/core/house-style/references/definitions-and-metrics.md +86 -0
- package/skills/core/house-style/references/document-workflow.md +193 -0
- package/skills/core/house-style/references/initialization.md +66 -0
- package/skills/core/house-style/references/persistence.md +9 -4
- package/skills/core/house-style/references/rules.md +11 -4
- package/skills/core/house-style/scripts/init-guidance.mjs +213 -0
- package/skills/core/house-style/templates/analytical-report.md +54 -0
- package/skills/core/house-style/templates/gap-analysis.md +1 -1
- package/skills/core/plain-language/SKILL.md +2 -2
- package/skills/core/polish-cn/SKILL.md +9 -2
- package/skills/core/reg-parser/SKILL.md +3 -3
- package/skills/core/terminology/SKILL.md +8 -4
- package/skills/core/write-doc/SKILL.md +40 -151
- package/skills/core/write-report/SKILL.md +129 -0
- package/skills/core/{write-doc → write-report}/references/compliance-reports.md +4 -4
- package/skills/engineering/better-review/SKILL.md +37 -37
- package/skills/engineering/better-review/references/passes.md +35 -41
- package/skills/engineering/better-review/references/result.md +10 -9
- package/skills/engineering/e2e-test/SKILL.md +17 -17
- package/skills/engineering/e2e-test/references/authoring/playwright-config.md +11 -13
- package/skills/engineering/e2e-test/references/presentation-sweep.md +7 -7
- package/skills/engineering/e2e-test/references/reviewing.md +9 -11
- package/skills/engineering/e2e-test/references/route-manifest.md +1 -1
- package/skills/engineering/engineering/SKILL.md +201 -0
- package/skills/engineering/engineering/references/engineering-conventions.md +32 -0
- package/skills/engineering/engineering/references/feature-delivery.md +8 -0
- package/skills/engineering/engineering/references/init-scope.md +45 -0
- package/skills/engineering/engineering/references/scope-management.md +85 -0
- package/skills/engineering/engineering/scripts/init.mjs +160 -0
- package/skills/engineering/{contributing → engineering}/scripts/persist-conventions.mjs +1 -1
- package/skills/engineering/engineering-runtime/SKILL.md +1 -1
- package/skills/engineering/engineering-runtime/coherence/workflow.json +19 -19
- package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +1 -1
- package/skills/engineering/ensure-coverage/SKILL.md +22 -109
- package/skills/engineering/ensure-coverage/references/breadth/coverage-ledger.md +7 -5
- package/skills/engineering/ensure-coverage/references/breadth/surface-baseline.md +4 -4
- package/skills/engineering/ensure-coverage/references/breadth/surface-discovery.md +3 -3
- package/skills/engineering/ensure-coverage/references/depth/characterization.md +4 -65
- package/skills/engineering/ensure-coverage/references/depth/grading.md +5 -7
- package/skills/engineering/ensure-coverage/references/depth/mock-policy.md +6 -12
- package/skills/engineering/ensure-coverage/references/enforcement/ci-contract.md +1 -1
- package/skills/engineering/ensure-coverage/references/presentation/axis.md +6 -6
- package/skills/engineering/ensure-coverage/references/process/audit-mode.md +7 -23
- package/skills/engineering/ensure-coverage/references/process/output-template.md +18 -133
- package/skills/engineering/ensure-coverage/references/process/test-strategy.md +7 -12
- package/skills/engineering/ensure-coverage/scripts/README.md +12 -4
- package/skills/engineering/golden-path/SKILL.md +1 -1
- package/skills/engineering/harness-runtime/SKILL.md +3 -1
- package/skills/engineering/harness-runtime/bin/workspaces.mjs +52 -0
- package/skills/engineering/harness-runtime/native-workspace.mjs +239 -0
- package/skills/engineering/harness-runtime/references/workspaces.md +78 -0
- package/skills/engineering/harness-runtime/workspace-lifecycle.mjs +2154 -0
- package/skills/engineering/resolve-issues/SKILL.md +40 -61
- package/skills/engineering/resolve-issues/references/ci.md +13 -3
- package/skills/engineering/resolve-issues/references/delivery-progress.md +4 -3
- package/skills/engineering/resolve-issues/references/done.md +11 -2
- package/skills/engineering/resolve-issues/references/expire.md +9 -2
- package/skills/engineering/resolve-issues/references/implementation.md +33 -17
- package/skills/engineering/resolve-issues/references/preflight.md +3 -1
- package/skills/engineering/resolve-issues/references/principles.md +52 -0
- package/skills/engineering/resolve-issues/references/review.md +25 -15
- package/skills/engineering/resolve-issues/references/task-plan.md +64 -27
- package/skills/engineering/resolve-issues/references/workspaces.md +164 -0
- package/skills/engineering/resolve-issues/scripts/cleanup-runs.mjs +185 -317
- package/skills/engineering/resolve-issues/scripts/next-actions.mjs +44 -7
- package/skills/engineering/resolve-issues/scripts/rehydrate.mjs +36 -2
- package/skills/engineering/resolve-issues/scripts/review-verdict.mjs +9 -8
- package/skills/engineering/resolve-issues/scripts/workspace-lifecycle.mjs +1 -0
- package/skills/engineering/resolve-release/SKILL.md +1 -1
- package/skills/engineering/rush-issues/SKILL.md +7 -5
- package/skills/engineering/rush-release/SKILL.md +1 -1
- package/skills/engineering/test-quality/SKILL.md +19 -4
- package/skills/engineering/test-quality/references/lint.md +1 -1
- package/skills/engineering/test-quality/references/principles.md +2 -2
- package/skills/engineering/test-quality/references/testing-conventions.md +1 -1
- package/skills/risk-compliance/compliance-audit/SKILL.md +4 -8
- package/skills/risk-compliance/compliance-audit/references/audience-and-reports.md +12 -0
- package/skills/risk-compliance/contract-compare/SKILL.md +17 -1
- package/skills/risk-compliance/contract-review/SKILL.md +6 -1
- package/skills/risk-compliance/contract-review/references/report-stage.md +17 -0
- package/skills/risk-compliance/doc-authenticity/SKILL.md +11 -1
- package/skills/risk-compliance/fraud-detection/LICENSE +3 -0
- package/skills/risk-compliance/fraud-detection/SKILL.md +148 -0
- package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/archetypes/first-party-fraud.md +3 -2
- package/skills/risk-compliance/fraud-detection/references/archetypes/third-party-fraud.md +47 -0
- package/skills/risk-compliance/fraud-detection/references/collection-payout-features.md +151 -0
- package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/contexts/bank-transfer.md +4 -3
- package/skills/risk-compliance/fraud-detection/references/contexts/card-payment.md +70 -0
- package/skills/risk-compliance/fraud-detection/references/contexts/payment-collection.md +34 -0
- package/skills/risk-compliance/fraud-detection/references/contexts/payout.md +34 -0
- package/skills/risk-compliance/fraud-detection/references/enrichment.md +74 -0
- package/skills/risk-compliance/fraud-detection/references/entities.md +48 -0
- package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/feature-engineering.md +69 -33
- package/skills/risk-compliance/fraud-detection/references/feature-sql.md +150 -0
- package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/report-rationale.md +4 -2
- package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/report-template.md +45 -23
- package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/review-checklist.md +60 -20
- package/skills/risk-compliance/fraud-detection/references/workflow.md +216 -0
- package/skills/risk-compliance/fraud-detection/release-check.mjs +95 -0
- package/skills/risk-compliance/payment-analysis/SKILL.md +36 -17
- package/skills/risk-compliance/payment-analysis/references/auth-rate-actions.md +2 -2
- package/skills/risk-compliance/payment-analysis/references/report-template.md +10 -2
- package/skills/risk-compliance/payment-analysis/references/visualization.md +11 -1
- package/skills/risk-compliance/portfolio-payment-analysis/LICENSE +3 -0
- package/skills/risk-compliance/portfolio-payment-analysis/SKILL.md +170 -0
- package/skills/risk-compliance/portfolio-payment-analysis/references/identity-and-reconciliation.md +46 -0
- package/skills/risk-compliance/portfolio-payment-analysis/references/metrics-and-reporting.md +170 -0
- package/skills/risk-compliance/portfolio-payment-analysis/references/risk-history.md +89 -0
- package/skills/risk-compliance/portfolio-payment-analysis/release-check.mjs +95 -0
- package/skills/risk-compliance/portfolio-payment-analysis/scripts/analyze.py +636 -0
- package/skills/risk-compliance/portfolio-payment-analysis/scripts/build_report.py +386 -0
- package/skills/risk-compliance/portfolio-payment-analysis/scripts/comparison_charts.py +425 -0
- package/skills/risk-compliance/portfolio-payment-analysis/scripts/csv_export.py +52 -0
- package/skills/risk-compliance/portfolio-payment-analysis/scripts/render.py +160 -0
- package/skills/risk-compliance/portfolio-payment-analysis/scripts/risk_history.py +435 -0
- package/skills/risk-compliance/similar-domain/SKILL.md +9 -4
- package/skills/risk-compliance/tld-detection/SKILL.md +124 -239
- package/skills/risk-compliance/tld-detection/agents/openai.yaml +1 -1
- package/skills/risk-compliance/tld-detection/references/clean.md +18 -51
- package/skills/risk-compliance/tld-detection/references/fx-conversion.md +56 -219
- package/skills/risk-compliance/tld-detection/references/metric-contract.md +109 -0
- package/skills/risk-compliance/tld-detection/references/pattern.md +99 -0
- package/skills/risk-compliance/tld-detection/references/report-template.md +49 -272
- package/skills/risk-compliance/tld-detection/scripts/detect_ladders.py +46 -13
- package/skills/risk-compliance/tld-detection/scripts/detect_patterns.py +346 -0
- package/skills/risk-compliance/tld-detection/scripts/detect_streams.py +51 -19
- package/skills/risk-compliance/tld-detection/scripts/profile_groups.py +109 -0
- package/skills/risk-compliance/tld-detection/scripts/render_charts.py +218 -0
- package/skills/risk-compliance/tld-detection/scripts/tld_common.py +243 -0
- package/skills/engineering/contributing/SKILL.md +0 -70
- package/skills/risk-compliance/fraud-analysis/SKILL.md +0 -114
- package/skills/risk-compliance/fraud-analysis/references/archetypes/third-party-fraud.md +0 -27
- package/skills/risk-compliance/fraud-analysis/references/contexts/card-payment.md +0 -30
- package/skills/risk-compliance/fraud-analysis/references/contexts/payment-collection.md +0 -20
- package/skills/risk-compliance/fraud-analysis/references/contexts/payout.md +0 -20
- package/skills/risk-compliance/fraud-analysis/references/workflow.md +0 -175
- package/skills/risk-compliance/tld-detection/references/chinese-polish.md +0 -55
- package/skills/risk-compliance/tld-detection/references/clustering.md +0 -235
- package/skills/risk-compliance/tld-detection/references/decision.md +0 -189
- package/skills/risk-compliance/tld-detection/references/streams.md +0 -141
- package/skills/risk-compliance/tld-detection/references/terminology.md +0 -95
- package/skills/risk-compliance/tld-detection/references/workflow.md +0 -95
- /package/skills/{engineering/contributing → core/write-report}/LICENSE +0 -0
- /package/skills/core/{write-doc → write-report}/references/reports/audit-report-template.md +0 -0
- /package/skills/core/{write-doc → write-report}/references/reports/external-assessment-template.md +0 -0
- /package/skills/core/{write-doc → write-report}/references/reports/management-report-template.md +0 -0
- /package/skills/core/{write-doc → write-report}/references/reports/vars-report-template.md +0 -0
- /package/skills/{engineering/contributing → core/write-report}/release-check.mjs +0 -0
- /package/skills/{risk-compliance/fraud-analysis → engineering/engineering}/LICENSE +0 -0
- /package/skills/engineering/{contributing → engineering}/references/tracker-conventions.md +0 -0
- /package/skills/{risk-compliance/fraud-analysis → engineering/engineering}/release-check.mjs +0 -0
- /package/skills/engineering/{contributing → engineering}/scripts/persist-conventions-core.mjs +0 -0
- /package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/archetypes/authorized-payment-scam.md +0 -0
- /package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/mechanisms/account-takeover.md +0 -0
- /package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/taxonomy.md +0 -0
- /package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/terminology.md +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: app-debug
|
|
3
3
|
description: "Diagnose runtime failures in deployed Cloud Run services — 5xx/422 errors, tracebacks, Next.js SSR/hydration or Server Action failures, DB/Secret Manager connection errors, cold-start timeouts, container OOM. NOT for build or deploy failures (cloud-debug)."
|
|
4
|
-
release-version: 0.
|
|
4
|
+
release-version: 0.45.1
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# App Debug
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: better-auth
|
|
3
3
|
description: "Add or debug Better Auth for Next.js App Router: login, sessions, protected routes, middleware, OAuth (including Feishu/Lark), cookies, Prisma, and Auth.js migration."
|
|
4
|
-
release-version: 0.
|
|
4
|
+
release-version: 0.45.1
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# Better Auth for Next.js
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: data-catalog
|
|
3
3
|
description: "Column definitions for warehouse tables, written inside the Spark/PySpark or SQL script that builds each table (CATALOG_TABLE / CATALOG_FIELDS, or a `-- @field` comment block), with a generated markdown catalog in docs/catalog/ and a CI check that fails a PR whose catalog is stale. Use when writing or changing a table script that adds, renames, removes, or changes the meaning of an output column or feature; when asked what a warehouse column or feature means; to set a repo up ('init the data catalog', 'generate the data dictionary', '字段字典', 'add catalog CI'); or to audit existing SQL or Spark scripts for undocumented columns. NOT for implementing or tuning the Spark job itself (pyspark), DAG wiring (airflow), or general workflow YAML (github-actions)."
|
|
4
|
-
release-version: 0.
|
|
4
|
+
release-version: 0.45.1
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# Data catalog
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: data-quality
|
|
3
3
|
description: "Check a published warehouse table's data against its data-catalog definitions with read-only SELECTs: columns never filled or stuck at one value, counts below zero, ratios outside 0–1, declared values that never appear and undeclared values that do. Then classify each finding as a wrong definition, wrong code, or wrong data. Use when asked whether a table's data is right, to profile or sanity-check a dwd/ads table, to find dead or constant columns, to check a column's values against its documented list, after a pipeline change ships, or when a data-catalog definition and the data might disagree. NOT for validating a Spark change before it ships (pyspark canary and reconciliation), writing definitions (data-catalog), or monitoring dashboards."
|
|
4
|
-
release-version: 0.
|
|
4
|
+
release-version: 0.45.1
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# Data quality
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ios-foundation
|
|
3
|
-
description: "Set up and prove an Expo / React Native native iOS workspace from
|
|
4
|
-
release-version: 0.
|
|
3
|
+
description: "Set up and prove an Expo / React Native native iOS workspace from an assigned or fresh isolated workspace to a manual Login smoke on an owned iOS Simulator: isolated workspace and private caches, Xcode/runtime/CocoaPods preflight, frozen install with `pod install --deployment`, platform-scoped prebuild, an ad-hoc-signed Debug simulator build, a private simulator by exact UDID, Metro on a free port, a disposable PostgreSQL backend with owned cleanup, and the evidence record (setup proof only; feature work reports the smoke result). Use for 'set up iOS', 'build the iOS app', 'run the app on the iOS simulator', 'add iOS to the React Native app', 'prove the iOS build', or when a pod install, xcodebuild, simulator boot, Metro connection or Keychain session fails. NOT for Android (android-foundation), signing, TestFlight or App Store release, or general workflow YAML (github-actions)."
|
|
4
|
+
release-version: 0.45.1
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# iOS foundation
|
|
@@ -22,16 +22,19 @@ Pick the mode first:
|
|
|
22
22
|
|
|
23
23
|
| Step | Read |
|
|
24
24
|
| --- | --- |
|
|
25
|
-
|
|
|
25
|
+
| Workspace resolution, then shell snippets for every stage | [references/workspace.md](references/workspace.md), [references/setup.md](references/setup.md) |
|
|
26
26
|
| Proving a product change on the simulator | [Feature work](#feature-work) |
|
|
27
27
|
| A stage failed | [Triage](#triage) |
|
|
28
28
|
| Writing up setup proof | [Evidence record](#evidence-record) |
|
|
29
29
|
|
|
30
30
|
## Rules
|
|
31
31
|
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
- Resolve the workspace first; [references/workspace.md](references/workspace.md) owns
|
|
33
|
+
precedence: an assigned isolated workspace and candidate HEAD win, feature work, fixes and
|
|
34
|
+
triage reuse the owned workspace, and standalone setup creates one managed workspace only
|
|
35
|
+
with no usable assignment. Read its actual HEAD; never substitute `origin/main`, a new
|
|
36
|
+
branch or another checkout. Never overwrite a peer's checkout, and never symlink another
|
|
37
|
+
worktree's `node_modules`, `Pods` or DerivedData.
|
|
35
38
|
- Tool shells keep no variables, functions or traps between calls. Write the environment
|
|
36
39
|
to `.cache/ios-env.sh` once and source it at the start of every command. Run the
|
|
37
40
|
disposable database and backend as one long-running command so its cleanup trap holds.
|
|
@@ -68,7 +71,8 @@ Pick the mode first:
|
|
|
68
71
|
|
|
69
72
|
Run them in this order. Stop at the first failure and triage it.
|
|
70
73
|
|
|
71
|
-
1. **
|
|
74
|
+
1. **Workspace and environment.** Resolve the assigned or standalone workspace
|
|
75
|
+
([references/workspace.md](references/workspace.md)) and export the private cache paths,
|
|
72
76
|
`CP_CACHE_DIR`, `CP_HOME_DIR` and the DerivedData path. Print `xcode-select -p`,
|
|
73
77
|
`xcodebuild -version`, `xcodebuild -showsdks`, `xcrun simctl list runtimes` and
|
|
74
78
|
`devicetypes`, and the node, pnpm, ruby and pod versions. Check that `pod --version`
|
|
@@ -110,7 +114,8 @@ Run them in this order. Stop at the first failure and triage it.
|
|
|
110
114
|
|
|
111
115
|
- Clean up only what this run started: the simulator by its exact UDID
|
|
112
116
|
(`simctl shutdown`, then `simctl delete`), the container by the ID in its CID file, and
|
|
113
|
-
the backend and Metro by stopping their own commands.
|
|
117
|
+
the backend and Metro by stopping their own commands. These are short-lived; the
|
|
118
|
+
development workspace, its Pods, DerivedData and private caches outlive them.
|
|
114
119
|
- Never use `simctl erase all`, `simctl shutdown all`, `killall Simulator`, broad `pkill`,
|
|
115
120
|
or `docker system prune`.
|
|
116
121
|
- Give every Docker startup, readiness and cleanup command a 30-second limit. Treat exit
|
|
@@ -167,8 +172,8 @@ reliable.
|
|
|
167
172
|
|
|
168
173
|
For setup proof only. Record:
|
|
169
174
|
|
|
170
|
-
- Source SHA
|
|
171
|
-
pnpm versions.
|
|
175
|
+
- Source SHA and the workspace root/path/base/branch/owner from the lifecycle record; host OS
|
|
176
|
+
and architecture, and the Xcode, iOS SDK, CocoaPods, Ruby, node and pnpm versions.
|
|
172
177
|
- The simulator runtime, device type and exact UDID (a run identifier, not a shared
|
|
173
178
|
resource), and the bundle identifier.
|
|
174
179
|
- The exit status of pod install, tests, typecheck, export and the native build.
|
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
# iOS foundation: shell snippets
|
|
2
2
|
|
|
3
3
|
Replace each `<placeholder>` with the repo's real value. Tool shells do not keep variables,
|
|
4
|
-
functions or traps between calls: step 1
|
|
5
|
-
|
|
6
|
-
(step 6) and Metro (step 7) each run as one long-running command
|
|
4
|
+
functions or traps between calls: step 1 resolves the workspace and writes
|
|
5
|
+
`.cache/ios-env.sh`, which also `cd`s into it; every later block starts by sourcing it by
|
|
6
|
+
absolute path. The backend (step 6) and Metro (step 7) each run as one long-running command
|
|
7
|
+
in its own background shell.
|
|
7
8
|
|
|
8
|
-
## 1.
|
|
9
|
+
## 1. Workspace and environment
|
|
10
|
+
|
|
11
|
+
Workspace precedence is owned by [workspace.md](workspace.md). Do not create a worktree,
|
|
12
|
+
switch branch, fetch or reset when the caller supplied an isolated workspace.
|
|
9
13
|
|
|
10
14
|
```bash
|
|
11
15
|
set -euo pipefail
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
ASSIGNED_WORKSPACE=<assigned-workspace-or-empty>
|
|
17
|
+
CANDIDATE_HEAD=<candidate-head-or-empty>
|
|
18
|
+
WORKSPACE="${ASSIGNED_WORKSPACE:?use the caller assignment or native-start result from workspace.md}"
|
|
19
|
+
test -d "$WORKSPACE"
|
|
20
|
+
git -C "$WORKSPACE" rev-parse --is-inside-work-tree >/dev/null
|
|
21
|
+
cd "$WORKSPACE"
|
|
22
|
+
actual_head="$(git rev-parse HEAD)"
|
|
23
|
+
test -z "$CANDIDATE_HEAD" || test "$actual_head" = "$(git rev-parse "$CANDIDATE_HEAD^{commit}")" || {
|
|
24
|
+
echo "workspace HEAD $actual_head != candidate $CANDIDATE_HEAD; report it; do not switch" >&2
|
|
25
|
+
exit 1
|
|
26
|
+
}
|
|
16
27
|
mkdir -p .cache
|
|
17
|
-
{ echo 'set -euo pipefail'; printf 'cd %q\n' "$PWD"; } > .cache/ios-env.sh
|
|
28
|
+
{ echo 'set -euo pipefail'; printf 'cd %q\n' "$PWD"; printf 'export IOS_SOURCE_HEAD=%q\n' "$actual_head"; } > .cache/ios-env.sh
|
|
18
29
|
cat >> .cache/ios-env.sh <<'ENV'
|
|
19
30
|
|
|
20
31
|
# Repo-specific names, read from the repo's app config, build script and device harness.
|
|
@@ -52,6 +63,11 @@ node --version; pnpm --version; ruby --version; pod --version
|
|
|
52
63
|
grep '^COCOAPODS:' "$IOS_DIR/Podfile.lock" # must match pod --version
|
|
53
64
|
```
|
|
54
65
|
|
|
66
|
+
The recorded standalone path is reused on retries; verify its lifecycle record names this
|
|
67
|
+
run, branch and base. If an assigned path is missing, is not a Git worktree, or is at another
|
|
68
|
+
revision, stop and report instead of creating another checkout. Never fall back to
|
|
69
|
+
`../<repo>-ios-setup`.
|
|
70
|
+
|
|
55
71
|
`xcode-select -p` must point into `Xcode.app`, not CommandLineTools. The workstation owner
|
|
56
72
|
selects Xcode and accepts its license; do not run `sudo xcodebuild -license` yourself.
|
|
57
73
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# iOS workspace lifecycle
|
|
2
|
+
|
|
3
|
+
The coordinator or caller owns workspace selection. Resolve it once, before any setup stage,
|
|
4
|
+
and record the result. This reference owns only that lifecycle: private caches, Xcode and
|
|
5
|
+
simulator isolation, and backend/Metro cleanup stay in [setup.md](setup.md) and
|
|
6
|
+
[Owned-resource cleanup](../SKILL.md#owned-resource-cleanup).
|
|
7
|
+
|
|
8
|
+
## Precedence
|
|
9
|
+
|
|
10
|
+
1. **Assigned workspace and candidate HEAD.** When the caller supplies an isolated workspace
|
|
11
|
+
and a candidate HEAD, use that workspace and that revision. Do not create a worktree or
|
|
12
|
+
branch, fetch, reset or switch revisions. Read `git rev-parse HEAD` and report it as the
|
|
13
|
+
source SHA. If it differs from the candidate HEAD, stop and report the mismatch; never
|
|
14
|
+
switch the workspace to match.
|
|
15
|
+
2. **Existing owned workspace.** Feature work, fixes, triage and any read-only investigation
|
|
16
|
+
reuse the workspace this run or unit already owns, with its private caches, Pods and
|
|
17
|
+
DerivedData. Do not make a second workspace for a retry, a correction or a read-only
|
|
18
|
+
investigation.
|
|
19
|
+
3. **Standalone fresh setup.** Only when the coordinator reports no usable assignment. Create
|
|
20
|
+
one managed workspace at
|
|
21
|
+
`<repo>/.resolve-issues/rush/<run-id>/units/native`. Never create a sibling checkout such
|
|
22
|
+
as `../<repo>-ios-setup`, and never add a second managed path for a retry.
|
|
23
|
+
|
|
24
|
+
Android and iOS setup in the same run share that one managed workspace; their private cache
|
|
25
|
+
subdirectories and short-lived processes stay separate.
|
|
26
|
+
|
|
27
|
+
An assigned path that is missing, is not a Git worktree, or is at another revision is not an
|
|
28
|
+
invitation to substitute a fresh checkout: stop and report it. For triage, name the actual
|
|
29
|
+
checkout and HEAD and confirm it contains the failing code; the repository root or another
|
|
30
|
+
checkout may lag the candidate.
|
|
31
|
+
|
|
32
|
+
## Record before creating
|
|
33
|
+
|
|
34
|
+
The bundled `scripts/workspace-runtime` is generated from the shared lifecycle
|
|
35
|
+
engine and ships with this skill. Only for standalone fresh setup, run:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
node <skill-base-dir>/scripts/workspace-runtime/bin/workspaces.mjs native-start --repo <canonical-root> \
|
|
39
|
+
--platform ios --target <verified-target-branch> --base <candidate-SHA> --json
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The command records root, owner, exact base, branch, workspace and disposable cache
|
|
43
|
+
paths before creating anything. Use its returned `workspace` as `ASSIGNED_WORKSPACE`
|
|
44
|
+
and `sourceSha` as `CANDIDATE_HEAD` in setup. Keep `run` and `runId`; repeat with
|
|
45
|
+
`--run-id <returned-run-id>` and the same base, target and kind for retries. Never
|
|
46
|
+
invoke it inside a coordinator-assigned unit. It never substitutes `origin/main`
|
|
47
|
+
for the requested source or resets an existing workspace.
|
|
48
|
+
For a package below the root, pass `--native-dir <package-root-containing-ios>`.
|
|
49
|
+
Declare other installs or build directories before use with repeated
|
|
50
|
+
`--disposable <exact-workspace-relative-path>`, including package-local `node_modules`.
|
|
51
|
+
|
|
52
|
+
## Development workspace vs short-lived processes
|
|
53
|
+
|
|
54
|
+
The workspace, its private caches under `.cache/`, its Pods and DerivedData are development
|
|
55
|
+
resources: retain them across retries and fixes, and keep them private to this workspace.
|
|
56
|
+
Never relocate them to a global cache or share them with a concurrent owner.
|
|
57
|
+
|
|
58
|
+
Simulators, backend and Metro children, containers and their databases are short-lived: start
|
|
59
|
+
only those the selected checks need and stop only the ones this run started, under the
|
|
60
|
+
existing [cleanup rules](../SKILL.md#owned-resource-cleanup). Never delete a worktree, remove
|
|
61
|
+
a cache with a chmod or `rm` pattern, or use a broad kill or `simctl erase all`.
|
|
62
|
+
|
|
63
|
+
A read-only investigation does not require a clean environment. Reuse the owned workspace and
|
|
64
|
+
its caches, create only the services the observation needs, and preserve the failed
|
|
65
|
+
environment's evidence. Do not rebuild, reinstall pods or re-provision merely because the
|
|
66
|
+
failure is being investigated.
|
|
67
|
+
|
|
68
|
+
## Completion
|
|
69
|
+
|
|
70
|
+
At the end of the assignment, return the workspace, its cache paths and any retained process
|
|
71
|
+
records to the coordinator for final cleanup. The coordinator owns deletion and retention.
|
|
72
|
+
Register the path with the run's cleanup owner so final cleanup can tell a delivered
|
|
73
|
+
standalone run from unfinished or unpublished work.
|
|
74
|
+
|
|
75
|
+
- A successful, delivered standalone run must not retain an unbounded cache: mark the
|
|
76
|
+
workspace and its disposable caches for final cleanup. After stopping its owned
|
|
77
|
+
services, run `node <skill-base-dir>/scripts/workspace-runtime/bin/workspaces.mjs native-finish --run <run> --json`
|
|
78
|
+
from the owner checkout. This uses the same cleanup engine as issue workflows.
|
|
79
|
+
- Unfinished or unpublished standalone work keeps the workspace and caches for the next
|
|
80
|
+
attempt; report what is retained and why.
|
|
81
|
+
|
|
82
|
+
The parent lifecycle record is authoritative for ownership, retention and final cleanup.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from 'node:child_process'
|
|
3
|
+
import { prepare, cleanup, recover, inventory } from '../workspace-lifecycle.mjs'
|
|
4
|
+
import { nativeStart, nativeFinish } from '../native-workspace.mjs'
|
|
5
|
+
|
|
6
|
+
const allowed = {
|
|
7
|
+
prepare: ['run', 'unit', 'revision', 'apply', 'json'],
|
|
8
|
+
cleanup: ['run', 'unit', 'revision', 'apply', 'json'],
|
|
9
|
+
recover: ['repo', 'json'],
|
|
10
|
+
inventory: ['repo', 'root', 'max-entries', 'max-depth', 'json'],
|
|
11
|
+
'native-start': ['repo', 'run-id', 'base', 'branch', 'target', 'platform', 'kind', 'native-dir', 'disposable', 'json'],
|
|
12
|
+
'native-finish': ['run', 'json'],
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function options(command, args) {
|
|
16
|
+
if (!Object.hasOwn(allowed, command)) throw new Error('expected prepare, cleanup, recover, inventory, native-start or native-finish')
|
|
17
|
+
const out = {}
|
|
18
|
+
for (let index = 0; index < args.length; index++) {
|
|
19
|
+
const name = args[index].replace(/^--/, '')
|
|
20
|
+
if (!args[index].startsWith('--') || !allowed[command].includes(name)) throw new Error(`invalid ${command} option: ${args[index]}`)
|
|
21
|
+
const key = name.replace(/-([a-z])/g, (_, value) => value.toUpperCase())
|
|
22
|
+
if (Object.hasOwn(out, key) && name !== 'disposable') throw new Error(`duplicate option: --${name}`)
|
|
23
|
+
if (name === 'apply' || name === 'json') { out[key] = true; continue }
|
|
24
|
+
const value = args[++index]
|
|
25
|
+
if (value === undefined || value.startsWith('--')) throw new Error(`--${name} needs a value`)
|
|
26
|
+
if (name === 'disposable') (out[key] ??= []).push(value)
|
|
27
|
+
else out[key] = value
|
|
28
|
+
}
|
|
29
|
+
for (const key of ['revision', 'maxEntries', 'maxDepth']) {
|
|
30
|
+
if (out[key] === undefined) continue
|
|
31
|
+
if (!/^\d+$/.test(out[key]) || !Number.isSafeInteger(Number(out[key]))) throw new Error(`invalid ${key}`)
|
|
32
|
+
out[key] = Number(out[key])
|
|
33
|
+
}
|
|
34
|
+
return out
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const [command, ...args] = process.argv.slice(2)
|
|
39
|
+
const opts = options(command, args)
|
|
40
|
+
let result
|
|
41
|
+
if (command === 'native-start') result = nativeStart(opts)
|
|
42
|
+
else if (command === 'native-finish') result = nativeFinish(opts)
|
|
43
|
+
else if (command === 'prepare') result = prepare(opts)
|
|
44
|
+
else if (command === 'cleanup') result = cleanup({ ...opts, runGh: (argv) => execFileSync('gh', argv, { encoding: 'utf8', timeout: 30_000, maxBuffer: 4 * 1024 * 1024, stdio: ['ignore', 'pipe', 'pipe'] }) })
|
|
45
|
+
else if (command === 'recover') result = recover(opts)
|
|
46
|
+
else result = inventory(opts)
|
|
47
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`)
|
|
48
|
+
process.exitCode = result.ok === false ? 1 : 0
|
|
49
|
+
} catch (error) {
|
|
50
|
+
process.stderr.write(`${error.message}\n`)
|
|
51
|
+
process.exitCode = 2
|
|
52
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { execFileSync, spawnSync } from 'node:child_process'
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmdirSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'
|
|
4
|
+
import { randomUUID } from 'node:crypto'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
|
|
7
|
+
const cli = fileURLToPath(new URL('./bin/workspaces.mjs', import.meta.url))
|
|
8
|
+
const git = (cwd, ...args) => execFileSync('git', ['-C', cwd, ...args], { encoding: 'utf8', timeout: 30_000, stdio: ['ignore', 'pipe', 'pipe'] }).trim()
|
|
9
|
+
const component = (value) => typeof value === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(value) && value !== '.' && value !== '..'
|
|
10
|
+
|
|
11
|
+
function stat(path) {
|
|
12
|
+
try { return lstatSync(path) } catch (error) { if (error.code === 'ENOENT') return null; throw error }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function noLinks(root, path) {
|
|
16
|
+
const rel = relative(root, path)
|
|
17
|
+
if (rel.startsWith(`..${sep}`) || rel === '..' || isAbsolute(rel)) throw new Error('workspace path escapes its repository')
|
|
18
|
+
let current = root
|
|
19
|
+
for (const part of rel.split(sep).filter(Boolean)) {
|
|
20
|
+
current = join(current, part)
|
|
21
|
+
if (stat(current)?.isSymbolicLink()) throw new Error(`symlink in managed path: ${current}`)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// This lock owns the plan, including initial creation. The engine independently locks
|
|
26
|
+
// workspace operations; its shorter lock cannot protect our read/modify/write cycle.
|
|
27
|
+
function withOwner(repo, run, operation) {
|
|
28
|
+
const locks = join(dirname(run), '.native-locks')
|
|
29
|
+
const lock = join(locks, basename(run))
|
|
30
|
+
noLinks(repo, run)
|
|
31
|
+
noLinks(repo, lock)
|
|
32
|
+
mkdirSync(locks, { recursive: true })
|
|
33
|
+
try { mkdirSync(lock) } catch (error) {
|
|
34
|
+
if (error.code !== 'EEXIST') throw error
|
|
35
|
+
let owner = 'unknown owner'
|
|
36
|
+
try { owner = `owner PID ${JSON.parse(readFileSync(join(lock, 'owner.json'), 'utf8')).pid ?? 'unknown'}` } catch { /* Unknown owners also block. */ }
|
|
37
|
+
throw new Error(`native run is locked at ${lock} (${owner}); locks are never reclaimed by age`)
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
writeFileSync(join(lock, 'owner.json'), `${JSON.stringify({ pid: process.pid, run, at: new Date().toISOString() })}\n`, { flag: 'wx', mode: 0o600 })
|
|
41
|
+
return operation()
|
|
42
|
+
} finally {
|
|
43
|
+
try { unlinkSync(join(lock, 'owner.json')) } catch (error) {
|
|
44
|
+
if (error.code !== 'ENOENT') throw error
|
|
45
|
+
} finally {
|
|
46
|
+
rmdirSync(lock)
|
|
47
|
+
try { rmdirSync(locks) } catch (error) { if (!['ENOTEMPTY', 'ENOENT', 'EEXIST'].includes(error.code)) throw error }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function planStore(repo, run) {
|
|
53
|
+
const file = join(run, 'task-plan.json')
|
|
54
|
+
const read = () => {
|
|
55
|
+
noLinks(repo, file)
|
|
56
|
+
return stat(file) ? readFileSync(file, 'utf8') : null
|
|
57
|
+
}
|
|
58
|
+
let expected = read()
|
|
59
|
+
const plan = expected === null ? null : JSON.parse(expected)
|
|
60
|
+
let revision = plan?.planRevision ?? -1
|
|
61
|
+
return {
|
|
62
|
+
plan,
|
|
63
|
+
save(next) {
|
|
64
|
+
if (read() !== expected || (expected !== null && JSON.parse(expected).planRevision !== revision)) {
|
|
65
|
+
throw new Error('native plan revision/content changed; refusing to overwrite another owner')
|
|
66
|
+
}
|
|
67
|
+
next.planRevision = revision + 1
|
|
68
|
+
const text = `${JSON.stringify(next, null, 2)}\n`
|
|
69
|
+
const temp = `${file}.${randomUUID()}.tmp`
|
|
70
|
+
try {
|
|
71
|
+
writeFileSync(temp, text, { flag: 'wx', mode: 0o600 })
|
|
72
|
+
if (read() !== expected) throw new Error('native plan changed before persist; refusing to overwrite another owner')
|
|
73
|
+
renameSync(temp, file)
|
|
74
|
+
expected = text
|
|
75
|
+
revision = next.planRevision
|
|
76
|
+
} finally {
|
|
77
|
+
if (stat(temp)) unlinkSync(temp)
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function ownedPlan(plan, repo, run) {
|
|
84
|
+
const unit = plan?.units?.[0]
|
|
85
|
+
if (plan?.skill !== 'native-setup' || plan.workspaceLifecycle !== 1 || plan.repoRoot !== repo || plan.runDir !== run ||
|
|
86
|
+
!Number.isSafeInteger(plan.planRevision) || plan.planRevision < 0 || plan.units?.length !== 1 || unit?.id !== 'native' ||
|
|
87
|
+
unit.workspace !== join(run, 'units', 'native') || unit.lifecycle?.version !== 1 ||
|
|
88
|
+
!['worktree', 'clone'].includes(unit.lifecycle.workspace?.kind) || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(unit.lifecycle.workspace?.baseSha ?? '') ||
|
|
89
|
+
unit.base !== unit.lifecycle.workspace.baseSha || !Array.isArray(unit.lifecycle.resources)) {
|
|
90
|
+
throw new Error('not an owned standalone native run; refusing to replace its record')
|
|
91
|
+
}
|
|
92
|
+
git(repo, 'check-ref-format', '--branch', plan.targetBranch)
|
|
93
|
+
git(repo, 'check-ref-format', '--branch', unit.branch)
|
|
94
|
+
noLinks(repo, unit.workspace)
|
|
95
|
+
return unit
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function relativePath(value, label, allowRoot = false) {
|
|
99
|
+
if (allowRoot && value === '.') return value
|
|
100
|
+
if (typeof value !== 'string' || !value || isAbsolute(value) || value.includes('\\') || /[\0*?\[\]]/.test(value) ||
|
|
101
|
+
value.split('/').some((part) => !part || part === '.' || part === '..' || part === '.git' || part === '.resolve-issues')) {
|
|
102
|
+
throw new Error(`${label} must be an exact relative path without .., symlinks, or managed roots`)
|
|
103
|
+
}
|
|
104
|
+
return value
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function resourcePaths(repo, workspace, platform, nativeDir, disposable) {
|
|
108
|
+
relativePath(nativeDir, '--native-dir', true)
|
|
109
|
+
if (!Array.isArray(disposable)) throw new Error('disposable must be an array of workspace-relative paths')
|
|
110
|
+
const paths = [...new Set(['.cache', 'node_modules',
|
|
111
|
+
...(platform === 'ios' ? ['ios/Pods'] : ['android/.gradle', 'android/build', 'android/app/build']).map((path) => nativeDir === '.' ? path : `${nativeDir}/${path}`),
|
|
112
|
+
...disposable.map((path) => relativePath(path, '--disposable'))])]
|
|
113
|
+
noLinks(repo, join(repo, nativeDir))
|
|
114
|
+
noLinks(repo, join(workspace, nativeDir))
|
|
115
|
+
for (const path of paths) {
|
|
116
|
+
noLinks(repo, join(repo, path))
|
|
117
|
+
noLinks(repo, join(workspace, path))
|
|
118
|
+
}
|
|
119
|
+
return paths
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function engine(args, repo) {
|
|
123
|
+
const result = spawnSync(process.execPath, [cli, ...args, '--json'], { cwd: repo, encoding: 'utf8', timeout: 120_000, maxBuffer: 8 * 1024 * 1024 })
|
|
124
|
+
let value
|
|
125
|
+
try { value = JSON.parse(result.stdout) } catch { /* Report the failed command below. */ }
|
|
126
|
+
if (result.status === 0 || (result.status === 1 && value?.action === 'retain' && (value.lifecycle || value.nextLifecycle))) return value
|
|
127
|
+
throw new Error(result.stderr?.trim() || result.stdout?.trim() || result.error?.message || 'workspace operation failed')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function returnedLifecycle(result) {
|
|
131
|
+
const lifecycle = result?.lifecycle ?? result?.nextLifecycle
|
|
132
|
+
if (!lifecycle || lifecycle.version !== 1) throw new Error('workspace helper returned no lifecycle transition; the recorded intent remains recoverable')
|
|
133
|
+
return lifecycle
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function nativeStart({ repo: input, runId, base, branch, target, platform, kind, nativeDir, disposable = [] }) {
|
|
137
|
+
if (!input || !['android', 'ios'].includes(platform) || (kind !== undefined && !['worktree', 'clone'].includes(kind))) throw new Error('native-start requires --repo and --platform android|ios; --kind is worktree or clone')
|
|
138
|
+
const repo = realpathSync(resolve(input))
|
|
139
|
+
if (realpathSync(git(repo, 'rev-parse', '--show-toplevel')) !== repo) throw new Error('--repo must name the owning checkout root')
|
|
140
|
+
const id = runId ?? `native-${new Date().toISOString().replace(/[^0-9]/g, '')}-${process.pid}`
|
|
141
|
+
if (!component(id)) throw new Error('invalid native run id')
|
|
142
|
+
const run = join(repo, '.resolve-issues', 'rush', id)
|
|
143
|
+
return withOwner(repo, run, () => {
|
|
144
|
+
const store = planStore(repo, run)
|
|
145
|
+
let plan = store.plan
|
|
146
|
+
if (!plan && stat(run)) throw new Error('existing run has no ownership record; refusing to adopt it')
|
|
147
|
+
let unit
|
|
148
|
+
if (plan) {
|
|
149
|
+
unit = ownedPlan(plan, repo, run)
|
|
150
|
+
if ((base !== undefined && git(repo, 'rev-parse', '--verify', `${base}^{commit}`) !== unit.base) ||
|
|
151
|
+
(target !== undefined && target !== plan.targetBranch) || (kind !== undefined && kind !== unit.lifecycle.workspace.kind) ||
|
|
152
|
+
(branch !== undefined && branch !== unit.branch) || (nativeDir !== undefined && nativeDir !== (unit.nativeDir ?? '.'))) {
|
|
153
|
+
throw new Error('existing native run belongs to a different setup; refusing to replace it')
|
|
154
|
+
}
|
|
155
|
+
nativeDir = unit.nativeDir ?? '.'
|
|
156
|
+
} else {
|
|
157
|
+
const baseSha = git(repo, 'rev-parse', '--verify', `${base ?? 'HEAD'}^{commit}`)
|
|
158
|
+
if (!target) {
|
|
159
|
+
try { target = git(repo, 'symbolic-ref', '--short', 'refs/remotes/origin/HEAD').replace(/^origin\//, '') } catch { throw new Error('record the repository target branch with --target; origin/HEAD is unavailable') }
|
|
160
|
+
}
|
|
161
|
+
branch ??= `native/${id}`
|
|
162
|
+
kind ??= 'worktree'
|
|
163
|
+
nativeDir ??= '.'
|
|
164
|
+
git(repo, 'check-ref-format', '--branch', target)
|
|
165
|
+
git(repo, 'check-ref-format', '--branch', branch)
|
|
166
|
+
unit = { id: 'native', title: 'Standalone native setup', workspace: join(run, 'units', 'native'), branch, base: baseSha, nativeDir, disposable: [], status: 'in-flight', stage: 'implement', worker: null,
|
|
167
|
+
lifecycle: { version: 1, workspace: { kind, state: 'preparing', baseSha }, resources: [], cleanup: { state: 'pending' } } }
|
|
168
|
+
plan = { skill: 'native-setup', workspaceLifecycle: 1, repoRoot: repo, runDir: run, targetBranch: target, planRevision: 0, units: [unit] }
|
|
169
|
+
}
|
|
170
|
+
if (unit.lifecycle.cleanup?.state === 'complete') throw new Error('this setup was cleaned; start a new run rather than recreating a used workspace')
|
|
171
|
+
if (!Array.isArray(disposable) || (unit.disposable !== undefined && !Array.isArray(unit.disposable))) throw new Error('disposable must be an array of workspace-relative paths')
|
|
172
|
+
const extras = [...new Set([...(unit.disposable ?? []), ...disposable])]
|
|
173
|
+
const paths = resourcePaths(repo, unit.workspace, platform, nativeDir, extras)
|
|
174
|
+
let changed = !store.plan || unit.nativeDir === undefined || JSON.stringify(unit.disposable) !== JSON.stringify(extras)
|
|
175
|
+
unit.nativeDir = nativeDir
|
|
176
|
+
unit.disposable = extras
|
|
177
|
+
for (const item of paths) {
|
|
178
|
+
const path = join(unit.workspace, item)
|
|
179
|
+
if (unit.lifecycle.resources.some((r) => r.path === path)) continue
|
|
180
|
+
unit.lifecycle.resources.push({ path, role: 'scratch', kind: 'directory', disposable: true, state: 'preparing' })
|
|
181
|
+
changed = true
|
|
182
|
+
}
|
|
183
|
+
if (!store.plan) mkdirSync(run)
|
|
184
|
+
if (changed) store.save(plan)
|
|
185
|
+
const result = engine(['prepare', '--run', run, '--unit', 'native', '--revision', String(plan.planRevision), '--apply'], repo)
|
|
186
|
+
unit.lifecycle = returnedLifecycle(result)
|
|
187
|
+
store.save(plan)
|
|
188
|
+
return { ...result, run, runId: id, workspace: unit.workspace, nativeDir, disposable: extras, sourceSha: git(unit.workspace, 'rev-parse', 'HEAD'), planRevision: plan.planRevision }
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function nativeFinish({ run: input }) {
|
|
193
|
+
if (!input) throw new Error('native-finish requires --run')
|
|
194
|
+
const run = resolve(input)
|
|
195
|
+
const repo = realpathSync(join(run, '..', '..', '..'))
|
|
196
|
+
if (!component(basename(run)) || run !== join(repo, '.resolve-issues', 'rush', basename(run))) throw new Error('not an owned standalone native run')
|
|
197
|
+
noLinks(repo, run)
|
|
198
|
+
if (!stat(run)?.isDirectory()) throw new Error('native run does not exist')
|
|
199
|
+
return withOwner(repo, run, () => {
|
|
200
|
+
const store = planStore(repo, run)
|
|
201
|
+
const plan = store.plan
|
|
202
|
+
const unit = ownedPlan(plan, repo, run)
|
|
203
|
+
// Completed and interrupted removals still go through the engine's postcondition
|
|
204
|
+
// checks, including owned resources outside the workspace and cleanup receipts.
|
|
205
|
+
const completeCleanup = () => {
|
|
206
|
+
const result = engine(['cleanup', '--run', run, '--unit', 'native', '--revision', String(plan.planRevision), '--apply'], repo)
|
|
207
|
+
const lifecycle = returnedLifecycle(result)
|
|
208
|
+
if (result.ok !== false && (lifecycle.cleanup?.state !== 'complete' || existsSync(unit.workspace) || lifecycle.resources.some((r) => existsSync(r.path)))) {
|
|
209
|
+
throw new Error('cleanup has not reached its postconditions; the recorded intent remains recoverable')
|
|
210
|
+
}
|
|
211
|
+
unit.lifecycle = lifecycle
|
|
212
|
+
store.save(plan)
|
|
213
|
+
return { ...result, run, planRevision: plan.planRevision }
|
|
214
|
+
}
|
|
215
|
+
if (unit.lifecycle.cleanup?.state === 'complete' || (!existsSync(unit.workspace) && unit.lifecycle.cleanup?.state === 'applying')) return completeCleanup()
|
|
216
|
+
const headSha = git(unit.workspace, 'rev-parse', 'HEAD')
|
|
217
|
+
const remote = git(repo, 'remote', 'get-url', 'origin')
|
|
218
|
+
git(unit.workspace, 'fetch', '--no-tags', '--', remote, `+refs/heads/${plan.targetBranch}:refs/remotes/native-delivery/${plan.targetBranch}`)
|
|
219
|
+
const targetRef = `refs/remotes/native-delivery/${plan.targetBranch}`
|
|
220
|
+
let landed = false
|
|
221
|
+
try { git(unit.workspace, 'merge-base', '--is-ancestor', headSha, targetRef); landed = true } catch (error) {
|
|
222
|
+
if (error.status !== 1) throw error
|
|
223
|
+
}
|
|
224
|
+
if (!landed) {
|
|
225
|
+
unit.headSha = headSha
|
|
226
|
+
const reason = 'native source has not landed on the recorded remote target; retain its environment'
|
|
227
|
+
unit.lifecycle.cleanup = { state: 'retained', headSha, reason }
|
|
228
|
+
store.save(plan)
|
|
229
|
+
return { ok: false, action: 'retain', outcome: 'retained', reason, run, lifecycle: unit.lifecycle, planRevision: plan.planRevision }
|
|
230
|
+
}
|
|
231
|
+
unit.headSha = headSha
|
|
232
|
+
unit.mergeSha = headSha
|
|
233
|
+
unit.status = 'succeeded'
|
|
234
|
+
unit.delivery = 'merged'
|
|
235
|
+
unit.lifecycle.cleanup = { state: 'applying', headSha }
|
|
236
|
+
store.save(plan)
|
|
237
|
+
return completeCleanup()
|
|
238
|
+
})
|
|
239
|
+
}
|