@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.
- package/.codex-plugin/plugin.json +1 -1
- package/.codex-plugin/resources.json +297 -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 +124 -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 +37 -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
|
@@ -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.
|
|
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.
|