@1aboveio/skills 1.0.9 → 1.0.11

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.
Files changed (213) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/.codex-plugin/resources.json +297 -216
  3. package/README.md +3 -3
  4. package/package.json +10 -8
  5. package/skills/app-dev/airflow/SKILL.md +1 -1
  6. package/skills/app-dev/android-foundation/SKILL.md +35 -37
  7. package/skills/app-dev/android-foundation/references/delivery-focus.md +4 -2
  8. package/skills/app-dev/android-foundation/references/feature-work.md +50 -0
  9. package/skills/app-dev/android-foundation/references/setup.md +24 -9
  10. package/skills/app-dev/android-foundation/references/workspace.md +81 -0
  11. package/skills/app-dev/android-foundation/scripts/workspace-runtime/bin/workspaces.mjs +52 -0
  12. package/skills/app-dev/android-foundation/scripts/workspace-runtime/native-workspace.mjs +239 -0
  13. package/skills/app-dev/android-foundation/scripts/workspace-runtime/workspace-lifecycle.mjs +2154 -0
  14. package/skills/app-dev/app-debug/SKILL.md +1 -1
  15. package/skills/app-dev/better-auth/SKILL.md +1 -1
  16. package/skills/app-dev/data-catalog/SKILL.md +1 -1
  17. package/skills/app-dev/data-quality/SKILL.md +1 -1
  18. package/skills/app-dev/ios-foundation/SKILL.md +15 -10
  19. package/skills/app-dev/ios-foundation/references/setup.md +25 -9
  20. package/skills/app-dev/ios-foundation/references/workspace.md +82 -0
  21. package/skills/app-dev/ios-foundation/scripts/workspace-runtime/bin/workspaces.mjs +52 -0
  22. package/skills/app-dev/ios-foundation/scripts/workspace-runtime/native-workspace.mjs +239 -0
  23. package/skills/app-dev/ios-foundation/scripts/workspace-runtime/workspace-lifecycle.mjs +2154 -0
  24. package/skills/app-dev/monorepo/SKILL.md +1 -1
  25. package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
  26. package/skills/app-dev/prisma-setup/SKILL.md +1 -1
  27. package/skills/app-dev/pyspark/SKILL.md +1 -1
  28. package/skills/app-dev/python-backend/SKILL.md +1 -1
  29. package/skills/app-dev/shadcn/SKILL.md +1 -1
  30. package/skills/app-dev/zod-v4/SKILL.md +1 -1
  31. package/skills/cicd/cloud-build/SKILL.md +1 -1
  32. package/skills/cicd/cloud-debug/SKILL.md +1 -1
  33. package/skills/cicd/cloud-deploy/SKILL.md +1 -1
  34. package/skills/cicd/docker/SKILL.md +1 -1
  35. package/skills/cicd/github-actions/SKILL.md +1 -1
  36. package/skills/cicd/github-flow/SKILL.md +1 -1
  37. package/skills/cicd/google-cloud/SKILL.md +1 -1
  38. package/skills/cicd/mergify/SKILL.md +1 -1
  39. package/skills/cicd/podman/SKILL.md +1 -1
  40. package/skills/content/article-review/SKILL.md +1 -1
  41. package/skills/content/article-to-image/SKILL.md +1 -1
  42. package/skills/content/flowchart-svg/SKILL.md +1 -1
  43. package/skills/content/image-gen/SKILL.md +1 -1
  44. package/skills/content/mermaid/SKILL.md +1 -1
  45. package/skills/content/wechat-article-writer/SKILL.md +1 -1
  46. package/skills/content/wechat-publisher/SKILL.md +1 -1
  47. package/skills/content/wechat-search/SKILL.md +1 -1
  48. package/skills/content/xai-search/SKILL.md +1 -1
  49. package/skills/core/distill-writing/SKILL.md +3 -3
  50. package/skills/core/distill-writing/references/extraction.md +1 -1
  51. package/skills/core/doc-to-md/SKILL.md +1 -1
  52. package/skills/core/editorial-review/SKILL.md +82 -12
  53. package/skills/core/editorial-review/references/checklist.md +26 -1
  54. package/skills/core/house-style/SKILL.md +8 -3
  55. package/skills/core/house-style/references/analytical-reporting.md +123 -0
  56. package/skills/core/house-style/references/analytical-writing-patterns.md +79 -0
  57. package/skills/core/house-style/references/analytical-writing-research.md +129 -0
  58. package/skills/core/{write-doc → house-style}/references/bilingual.md +7 -5
  59. package/skills/core/house-style/references/definitions-and-metrics.md +86 -0
  60. package/skills/core/house-style/references/document-workflow.md +193 -0
  61. package/skills/core/house-style/references/initialization.md +66 -0
  62. package/skills/core/house-style/references/persistence.md +9 -4
  63. package/skills/core/house-style/references/rules.md +11 -4
  64. package/skills/core/house-style/scripts/init-guidance.mjs +213 -0
  65. package/skills/core/house-style/templates/analytical-report.md +54 -0
  66. package/skills/core/house-style/templates/gap-analysis.md +1 -1
  67. package/skills/core/plain-language/SKILL.md +2 -2
  68. package/skills/core/polish-cn/SKILL.md +9 -2
  69. package/skills/core/reg-parser/SKILL.md +3 -3
  70. package/skills/core/terminology/SKILL.md +8 -4
  71. package/skills/core/write-doc/SKILL.md +40 -151
  72. package/skills/core/write-report/SKILL.md +129 -0
  73. package/skills/core/{write-doc → write-report}/references/compliance-reports.md +4 -4
  74. package/skills/engineering/better-review/SKILL.md +37 -37
  75. package/skills/engineering/better-review/references/passes.md +35 -41
  76. package/skills/engineering/better-review/references/result.md +10 -9
  77. package/skills/engineering/e2e-test/SKILL.md +17 -17
  78. package/skills/engineering/e2e-test/references/authoring/playwright-config.md +11 -13
  79. package/skills/engineering/e2e-test/references/presentation-sweep.md +7 -7
  80. package/skills/engineering/e2e-test/references/reviewing.md +9 -11
  81. package/skills/engineering/e2e-test/references/route-manifest.md +1 -1
  82. package/skills/engineering/engineering/SKILL.md +124 -0
  83. package/skills/engineering/engineering/references/engineering-conventions.md +32 -0
  84. package/skills/engineering/engineering/references/feature-delivery.md +8 -0
  85. package/skills/engineering/engineering/references/init-scope.md +37 -0
  86. package/skills/engineering/engineering/scripts/init.mjs +160 -0
  87. package/skills/engineering/{contributing → engineering}/scripts/persist-conventions.mjs +1 -1
  88. package/skills/engineering/engineering-runtime/SKILL.md +1 -1
  89. package/skills/engineering/engineering-runtime/coherence/workflow.json +19 -19
  90. package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +1 -1
  91. package/skills/engineering/ensure-coverage/SKILL.md +22 -109
  92. package/skills/engineering/ensure-coverage/references/breadth/coverage-ledger.md +7 -5
  93. package/skills/engineering/ensure-coverage/references/breadth/surface-baseline.md +4 -4
  94. package/skills/engineering/ensure-coverage/references/breadth/surface-discovery.md +3 -3
  95. package/skills/engineering/ensure-coverage/references/depth/characterization.md +4 -65
  96. package/skills/engineering/ensure-coverage/references/depth/grading.md +5 -7
  97. package/skills/engineering/ensure-coverage/references/depth/mock-policy.md +6 -12
  98. package/skills/engineering/ensure-coverage/references/enforcement/ci-contract.md +1 -1
  99. package/skills/engineering/ensure-coverage/references/presentation/axis.md +6 -6
  100. package/skills/engineering/ensure-coverage/references/process/audit-mode.md +7 -23
  101. package/skills/engineering/ensure-coverage/references/process/output-template.md +18 -133
  102. package/skills/engineering/ensure-coverage/references/process/test-strategy.md +7 -12
  103. package/skills/engineering/ensure-coverage/scripts/README.md +12 -4
  104. package/skills/engineering/golden-path/SKILL.md +1 -1
  105. package/skills/engineering/harness-runtime/SKILL.md +3 -1
  106. package/skills/engineering/harness-runtime/bin/workspaces.mjs +52 -0
  107. package/skills/engineering/harness-runtime/native-workspace.mjs +239 -0
  108. package/skills/engineering/harness-runtime/references/workspaces.md +78 -0
  109. package/skills/engineering/harness-runtime/workspace-lifecycle.mjs +2154 -0
  110. package/skills/engineering/resolve-issues/SKILL.md +40 -61
  111. package/skills/engineering/resolve-issues/references/ci.md +13 -3
  112. package/skills/engineering/resolve-issues/references/delivery-progress.md +4 -3
  113. package/skills/engineering/resolve-issues/references/done.md +11 -2
  114. package/skills/engineering/resolve-issues/references/expire.md +9 -2
  115. package/skills/engineering/resolve-issues/references/implementation.md +33 -17
  116. package/skills/engineering/resolve-issues/references/preflight.md +3 -1
  117. package/skills/engineering/resolve-issues/references/principles.md +52 -0
  118. package/skills/engineering/resolve-issues/references/review.md +25 -15
  119. package/skills/engineering/resolve-issues/references/task-plan.md +64 -27
  120. package/skills/engineering/resolve-issues/references/workspaces.md +164 -0
  121. package/skills/engineering/resolve-issues/scripts/cleanup-runs.mjs +185 -317
  122. package/skills/engineering/resolve-issues/scripts/next-actions.mjs +44 -7
  123. package/skills/engineering/resolve-issues/scripts/rehydrate.mjs +36 -2
  124. package/skills/engineering/resolve-issues/scripts/review-verdict.mjs +9 -8
  125. package/skills/engineering/resolve-issues/scripts/workspace-lifecycle.mjs +1 -0
  126. package/skills/engineering/resolve-release/SKILL.md +1 -1
  127. package/skills/engineering/rush-issues/SKILL.md +7 -5
  128. package/skills/engineering/rush-release/SKILL.md +1 -1
  129. package/skills/engineering/test-quality/SKILL.md +19 -4
  130. package/skills/engineering/test-quality/references/lint.md +1 -1
  131. package/skills/engineering/test-quality/references/principles.md +2 -2
  132. package/skills/engineering/test-quality/references/testing-conventions.md +1 -1
  133. package/skills/risk-compliance/compliance-audit/SKILL.md +4 -8
  134. package/skills/risk-compliance/compliance-audit/references/audience-and-reports.md +12 -0
  135. package/skills/risk-compliance/contract-compare/SKILL.md +17 -1
  136. package/skills/risk-compliance/contract-review/SKILL.md +6 -1
  137. package/skills/risk-compliance/contract-review/references/report-stage.md +17 -0
  138. package/skills/risk-compliance/doc-authenticity/SKILL.md +11 -1
  139. package/skills/risk-compliance/fraud-detection/LICENSE +3 -0
  140. package/skills/risk-compliance/fraud-detection/SKILL.md +148 -0
  141. package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/archetypes/first-party-fraud.md +3 -2
  142. package/skills/risk-compliance/fraud-detection/references/archetypes/third-party-fraud.md +47 -0
  143. package/skills/risk-compliance/fraud-detection/references/collection-payout-features.md +151 -0
  144. package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/contexts/bank-transfer.md +4 -3
  145. package/skills/risk-compliance/fraud-detection/references/contexts/card-payment.md +70 -0
  146. package/skills/risk-compliance/fraud-detection/references/contexts/payment-collection.md +34 -0
  147. package/skills/risk-compliance/fraud-detection/references/contexts/payout.md +34 -0
  148. package/skills/risk-compliance/fraud-detection/references/enrichment.md +74 -0
  149. package/skills/risk-compliance/fraud-detection/references/entities.md +48 -0
  150. package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/feature-engineering.md +69 -33
  151. package/skills/risk-compliance/fraud-detection/references/feature-sql.md +150 -0
  152. package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/report-rationale.md +4 -2
  153. package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/report-template.md +45 -23
  154. package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/review-checklist.md +60 -20
  155. package/skills/risk-compliance/fraud-detection/references/workflow.md +216 -0
  156. package/skills/risk-compliance/fraud-detection/release-check.mjs +95 -0
  157. package/skills/risk-compliance/payment-analysis/SKILL.md +36 -17
  158. package/skills/risk-compliance/payment-analysis/references/auth-rate-actions.md +2 -2
  159. package/skills/risk-compliance/payment-analysis/references/report-template.md +10 -2
  160. package/skills/risk-compliance/payment-analysis/references/visualization.md +11 -1
  161. package/skills/risk-compliance/portfolio-payment-analysis/LICENSE +3 -0
  162. package/skills/risk-compliance/portfolio-payment-analysis/SKILL.md +170 -0
  163. package/skills/risk-compliance/portfolio-payment-analysis/references/identity-and-reconciliation.md +46 -0
  164. package/skills/risk-compliance/portfolio-payment-analysis/references/metrics-and-reporting.md +170 -0
  165. package/skills/risk-compliance/portfolio-payment-analysis/references/risk-history.md +89 -0
  166. package/skills/risk-compliance/portfolio-payment-analysis/release-check.mjs +95 -0
  167. package/skills/risk-compliance/portfolio-payment-analysis/scripts/analyze.py +636 -0
  168. package/skills/risk-compliance/portfolio-payment-analysis/scripts/build_report.py +386 -0
  169. package/skills/risk-compliance/portfolio-payment-analysis/scripts/comparison_charts.py +425 -0
  170. package/skills/risk-compliance/portfolio-payment-analysis/scripts/csv_export.py +52 -0
  171. package/skills/risk-compliance/portfolio-payment-analysis/scripts/render.py +160 -0
  172. package/skills/risk-compliance/portfolio-payment-analysis/scripts/risk_history.py +435 -0
  173. package/skills/risk-compliance/similar-domain/SKILL.md +9 -4
  174. package/skills/risk-compliance/tld-detection/SKILL.md +124 -239
  175. package/skills/risk-compliance/tld-detection/agents/openai.yaml +1 -1
  176. package/skills/risk-compliance/tld-detection/references/clean.md +18 -51
  177. package/skills/risk-compliance/tld-detection/references/fx-conversion.md +56 -219
  178. package/skills/risk-compliance/tld-detection/references/metric-contract.md +109 -0
  179. package/skills/risk-compliance/tld-detection/references/pattern.md +99 -0
  180. package/skills/risk-compliance/tld-detection/references/report-template.md +49 -272
  181. package/skills/risk-compliance/tld-detection/scripts/detect_ladders.py +46 -13
  182. package/skills/risk-compliance/tld-detection/scripts/detect_patterns.py +346 -0
  183. package/skills/risk-compliance/tld-detection/scripts/detect_streams.py +51 -19
  184. package/skills/risk-compliance/tld-detection/scripts/profile_groups.py +109 -0
  185. package/skills/risk-compliance/tld-detection/scripts/render_charts.py +218 -0
  186. package/skills/risk-compliance/tld-detection/scripts/tld_common.py +243 -0
  187. package/skills/engineering/contributing/SKILL.md +0 -70
  188. package/skills/risk-compliance/fraud-analysis/SKILL.md +0 -114
  189. package/skills/risk-compliance/fraud-analysis/references/archetypes/third-party-fraud.md +0 -27
  190. package/skills/risk-compliance/fraud-analysis/references/contexts/card-payment.md +0 -30
  191. package/skills/risk-compliance/fraud-analysis/references/contexts/payment-collection.md +0 -20
  192. package/skills/risk-compliance/fraud-analysis/references/contexts/payout.md +0 -20
  193. package/skills/risk-compliance/fraud-analysis/references/workflow.md +0 -175
  194. package/skills/risk-compliance/tld-detection/references/chinese-polish.md +0 -55
  195. package/skills/risk-compliance/tld-detection/references/clustering.md +0 -235
  196. package/skills/risk-compliance/tld-detection/references/decision.md +0 -189
  197. package/skills/risk-compliance/tld-detection/references/streams.md +0 -141
  198. package/skills/risk-compliance/tld-detection/references/terminology.md +0 -95
  199. package/skills/risk-compliance/tld-detection/references/workflow.md +0 -95
  200. /package/skills/{engineering/contributing → core/write-report}/LICENSE +0 -0
  201. /package/skills/core/{write-doc → write-report}/references/reports/audit-report-template.md +0 -0
  202. /package/skills/core/{write-doc → write-report}/references/reports/external-assessment-template.md +0 -0
  203. /package/skills/core/{write-doc → write-report}/references/reports/management-report-template.md +0 -0
  204. /package/skills/core/{write-doc → write-report}/references/reports/vars-report-template.md +0 -0
  205. /package/skills/{engineering/contributing → core/write-report}/release-check.mjs +0 -0
  206. /package/skills/{risk-compliance/fraud-analysis → engineering/engineering}/LICENSE +0 -0
  207. /package/skills/engineering/{contributing → engineering}/references/tracker-conventions.md +0 -0
  208. /package/skills/{risk-compliance/fraud-analysis → engineering/engineering}/release-check.mjs +0 -0
  209. /package/skills/engineering/{contributing → engineering}/scripts/persist-conventions-core.mjs +0 -0
  210. /package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/archetypes/authorized-payment-scam.md +0 -0
  211. /package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/mechanisms/account-takeover.md +0 -0
  212. /package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/taxonomy.md +0 -0
  213. /package/skills/risk-compliance/{fraud-analysis → fraud-detection}/references/terminology.md +0 -0
@@ -3,7 +3,7 @@ name: harness-runtime
3
3
  description: Internal import-only library shared by first-party engineering packages. Not a workflow skill; never select or invoke it as a user capability.
4
4
  disable-model-invocation: true
5
5
  user-invocable: false
6
- release-version: 0.44.0
6
+ release-version: 0.45.0
7
7
  ---
8
8
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
9
9
  # Harness Runtime
@@ -17,3 +17,5 @@ This support-only runtime package exposes shared programmatic contracts plus can
17
17
  - `index.mjs` exports neutral constructors plus pure question planning and response normalization.
18
18
  - `discover-models.mjs` enumerates model evidence; `model-catalog.mjs` manages per-harness classifications.
19
19
  - `references/model-catalog.md` documents catalog policy; `bin/` contains the installed command wrappers.
20
+ - `workspace-lifecycle.mjs` and `bin/workspaces.mjs` own workspace preparation and reclamation;
21
+ read [references/workspaces.md](references/workspaces.md) for standalone native setup.
@@ -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
+ }
@@ -0,0 +1,78 @@
1
+ # Owned Workspaces
2
+
3
+ The same lifecycle engine serves `resolve-issues` and standalone native setup.
4
+ It creates one recorded workspace, reuses it, and removes only owned, delivered,
5
+ idle environments. It never deletes legacy project folders by age or naming pattern.
6
+
7
+ ## Assigned Work
8
+
9
+ An orchestrator supplies its prepared workspace and candidate SHA. Use both as
10
+ given. Do not run `native-start` inside an assigned unit, create a second checkout,
11
+ switch branches or reset the source. Return resource paths to that orchestrator;
12
+ only it writes its task plan and decides when delivery permits cleanup.
13
+
14
+ Keep mutable caches private and declare their exact disposable paths before use.
15
+ An ignored file is not automatically disposable. Shared SDKs and other projects
16
+ are never owned resources of a unit.
17
+
18
+ ## Standalone Setup
19
+
20
+ Resolve this installed runtime from the caller's selected installation, not a
21
+ filesystem search or another cached version. With no assigned or existing owned
22
+ workspace, run:
23
+
24
+ ```bash
25
+ node <harness-runtime>/bin/workspaces.mjs native-start --repo <canonical-repo-root> \
26
+ --platform android --target <verified-target-branch> --base <candidate-SHA> --json
27
+ ```
28
+
29
+ For iOS, use `--platform ios`. For a kernel-confined Git-mutating worker, choose
30
+ `--kind clone` before preparing; never disable confinement to use a linked tree.
31
+ For a native package below the root, pass `--native-dir apps/native` (the package
32
+ root containing `ios/` or `android/`). Register other outputs before installation
33
+ with repeated `--disposable <workspace-relative-directory>`, including every
34
+ package-local `node_modules` directory the install will create. No glob discovery
35
+ or ignored-file heuristic establishes disposal authority.
36
+ The owner root can remain dirty: creation uses the recorded commit, not its local
37
+ uncommitted files. The command does not fetch or change the owner checkout.
38
+
39
+ Read the JSON result. `run`, `runId`, `workspace`, `sourceSha`, and `planRevision`
40
+ identify the assignment. Pass `workspace` and `sourceSha` to native setup. Reuse
41
+ the returned `runId` with `--run-id` and the same base, target and kind for retries
42
+ and for adding the other platform to the same unit. Never create a run per attempt.
43
+
44
+ `native-start` records intent in
45
+ `<repo>/.resolve-issues/rush/<run-id>/task-plan.json` before preparation. That small
46
+ record is the standalone owner's authority, not a second manifest for an issue
47
+ workflow. The command records the workspace and platform-specific disposable
48
+ caches, then persists the lifecycle engine's returned identities. A conflicting
49
+ or missing previously used workspace is reported, never reset or recreated.
50
+
51
+ ## Finish And Recover
52
+
53
+ Stop the assignment's native services, devices and containers through their own
54
+ owners, then run from the owner checkout, not the workspace being removed:
55
+
56
+ ```bash
57
+ node <harness-runtime>/bin/workspaces.mjs native-finish --run <returned-run-path> --json
58
+ ```
59
+
60
+ The source must be on the recorded remote target. Dirty source, unpublished work,
61
+ unknown ignored files, required evidence inside a disposable path, or live use
62
+ retains the complete environment with a reason. A successful removal preserves
63
+ the small plan and cleanup receipt; it does not leave installed dependencies and
64
+ build caches. Retention is not successful cleanup. Keep the returned record and
65
+ report its reason without inventing a passing validation result.
66
+
67
+ An interrupted operation remains in the same plan. Inspect it without deleting:
68
+
69
+ ```bash
70
+ node <harness-runtime>/bin/workspaces.mjs recover --repo <canonical-repo-root> --json
71
+ node <harness-runtime>/bin/workspaces.mjs inventory --repo <canonical-repo-root> \
72
+ --root <explicit-legacy-workspace-root> --max-entries 5000 --max-depth 8 --json
73
+ ```
74
+
75
+ Discovery does not activate a workflow, adopt a legacy directory or authorize
76
+ deletion. Never turn an unknown or missing path into a completed cleanup result.
77
+ Standalone commands hold a per-run owner lock across plan updates and engine calls;
78
+ they refuse concurrent owners and never reclaim an unknown lock by age.