@stravica-ai/digital-operator-pa 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +170 -0
- package/bin/desk +9 -0
- package/bin/distill +2 -0
- package/bin/estate-sync +2 -0
- package/bin/launch +2 -0
- package/bin/memory-sync +2 -0
- package/bin/migrate-inspect +2 -0
- package/bin/pa +2 -0
- package/bin/pa-coordinator-hook +2 -0
- package/bin/pa-init +2 -0
- package/bin/research +2 -0
- package/bin/scheduler +2 -0
- package/bin/secrets +2 -0
- package/bin/slice +2 -0
- package/bin/smoke +9 -0
- package/bin/sync +2 -0
- package/dist/engineering-pa-cli/README.md +205 -0
- package/dist/engineering-pa-cli/bin/estate-sync +2 -0
- package/dist/engineering-pa-cli/bin/launch +2 -0
- package/dist/engineering-pa-cli/bin/pa +2 -0
- package/dist/engineering-pa-cli/bin/pa-coordinator-hook +12 -0
- package/dist/engineering-pa-cli/bin/research +2 -0
- package/dist/engineering-pa-cli/bin/scheduler +2 -0
- package/dist/engineering-pa-cli/bin/secrets +2 -0
- package/dist/engineering-pa-cli/bin/smoke +9 -0
- package/dist/engineering-pa-cli/bin/sync +2 -0
- package/dist/engineering-pa-cli/config/coordinator-allowlist.json +118 -0
- package/dist/engineering-pa-cli/docs/coordinator-hook.md +211 -0
- package/dist/engineering-pa-cli/docs/estate-sync.md +412 -0
- package/dist/engineering-pa-cli/docs/gsc.md +175 -0
- package/dist/engineering-pa-cli/docs/lint-linkedin.md +139 -0
- package/dist/engineering-pa-cli/docs/secrets.md +221 -0
- package/dist/engineering-pa-cli/docs/smoke.md +113 -0
- package/dist/engineering-pa-cli/docs/staging-alias.md +114 -0
- package/dist/engineering-pa-cli/index.mjs +17 -0
- package/dist/engineering-pa-cli/package.json +37 -0
- package/dist/engineering-pa-cli/src/autonomy/cli.mjs +329 -0
- package/dist/engineering-pa-cli/src/autonomy/envelope.mjs +301 -0
- package/dist/engineering-pa-cli/src/coordinator-hook/cli.mjs +140 -0
- package/dist/engineering-pa-cli/src/coordinator-hook/config.mjs +134 -0
- package/dist/engineering-pa-cli/src/coordinator-hook/message.mjs +47 -0
- package/dist/engineering-pa-cli/src/coordinator-hook/policy.mjs +211 -0
- package/dist/engineering-pa-cli/src/coordinator-hook/repo-root.mjs +44 -0
- package/dist/engineering-pa-cli/src/coordinator-hook/self.mjs +30 -0
- package/dist/engineering-pa-cli/src/environment/cli.mjs +287 -0
- package/dist/engineering-pa-cli/src/environment/probes.mjs +302 -0
- package/dist/engineering-pa-cli/src/gsc/auth.mjs +148 -0
- package/dist/engineering-pa-cli/src/gsc/cli.mjs +93 -0
- package/dist/engineering-pa-cli/src/gsc/cloudflare.mjs +318 -0
- package/dist/engineering-pa-cli/src/gsc/commands/add-property.mjs +70 -0
- package/dist/engineering-pa-cli/src/gsc/commands/list-properties.mjs +33 -0
- package/dist/engineering-pa-cli/src/gsc/commands/pull-analytics.mjs +130 -0
- package/dist/engineering-pa-cli/src/gsc/commands/submit-sitemap.mjs +41 -0
- package/dist/engineering-pa-cli/src/gsc/commands/verify-via-cf-dns.mjs +189 -0
- package/dist/engineering-pa-cli/src/knowledge/call-log.mjs +59 -0
- package/dist/engineering-pa-cli/src/knowledge/cli.mjs +242 -0
- package/dist/engineering-pa-cli/src/knowledge/eval.mjs +446 -0
- package/dist/engineering-pa-cli/src/knowledge/formatters.mjs +331 -0
- package/dist/engineering-pa-cli/src/knowledge/graph.mjs +710 -0
- package/dist/engineering-pa-cli/src/launch/README.md +214 -0
- package/dist/engineering-pa-cli/src/launch/cli.mjs +51 -0
- package/dist/engineering-pa-cli/src/launch/commands/launch.mjs +106 -0
- package/dist/engineering-pa-cli/src/launch/identity.mjs +43 -0
- package/dist/engineering-pa-cli/src/launch/planner.mjs +44 -0
- package/dist/engineering-pa-cli/src/launch/repo-root.mjs +53 -0
- package/dist/engineering-pa-cli/src/launch/runner.mjs +147 -0
- package/dist/engineering-pa-cli/src/launch/task-loader.mjs +85 -0
- package/dist/engineering-pa-cli/src/lint/cli.mjs +61 -0
- package/dist/engineering-pa-cli/src/lint/commands/linkedin.mjs +133 -0
- package/dist/engineering-pa-cli/src/lint/detectors.mjs +90 -0
- package/dist/engineering-pa-cli/src/lint/linter.mjs +69 -0
- package/dist/engineering-pa-cli/src/lint/reporter.mjs +32 -0
- package/dist/engineering-pa-cli/src/lint/rules.mjs +132 -0
- package/dist/engineering-pa-cli/src/pa/cli.mjs +369 -0
- package/dist/engineering-pa-cli/src/pa/doc-groups.mjs +164 -0
- package/dist/engineering-pa-cli/src/pa/io.mjs +80 -0
- package/dist/engineering-pa-cli/src/pa/project-cli.mjs +385 -0
- package/dist/engineering-pa-cli/src/pa/projects.mjs +352 -0
- package/dist/engineering-pa-cli/src/pa/repo-root.mjs +64 -0
- package/dist/engineering-pa-cli/src/pa/session.mjs +254 -0
- package/dist/engineering-pa-cli/src/pa/work-cli.mjs +164 -0
- package/dist/engineering-pa-cli/src/pa/work.mjs +196 -0
- package/dist/engineering-pa-cli/src/pa/workstreams.mjs +201 -0
- package/dist/engineering-pa-cli/src/research/README.md +360 -0
- package/dist/engineering-pa-cli/src/research/briefs/extractor.md +103 -0
- package/dist/engineering-pa-cli/src/research/briefs/searcher.md +113 -0
- package/dist/engineering-pa-cli/src/research/briefs/synthesizer.md +124 -0
- package/dist/engineering-pa-cli/src/research/briefs/verifier.md +86 -0
- package/dist/engineering-pa-cli/src/research/cli.mjs +141 -0
- package/dist/engineering-pa-cli/src/research/commands/gates.mjs +176 -0
- package/dist/engineering-pa-cli/src/research/commands/list.mjs +56 -0
- package/dist/engineering-pa-cli/src/research/commands/mark.mjs +97 -0
- package/dist/engineering-pa-cli/src/research/commands/paths.mjs +69 -0
- package/dist/engineering-pa-cli/src/research/commands/prune.mjs +86 -0
- package/dist/engineering-pa-cli/src/research/commands/run.mjs +94 -0
- package/dist/engineering-pa-cli/src/research/commands/show.mjs +43 -0
- package/dist/engineering-pa-cli/src/research/docs/operator-present-flow.md +120 -0
- package/dist/engineering-pa-cli/src/research/format.mjs +59 -0
- package/dist/engineering-pa-cli/src/research/promote.mjs +260 -0
- package/dist/engineering-pa-cli/src/research/schema/finding.schema.json +55 -0
- package/dist/engineering-pa-cli/src/research/schema/gates.schema.json +34 -0
- package/dist/engineering-pa-cli/src/research/schema/manifest.schema.json +59 -0
- package/dist/engineering-pa-cli/src/research/schema/plan.schema.json +41 -0
- package/dist/engineering-pa-cli/src/research/schema/sources.schema.json +31 -0
- package/dist/engineering-pa-cli/src/research/store/findings.mjs +61 -0
- package/dist/engineering-pa-cli/src/research/store/gates.mjs +73 -0
- package/dist/engineering-pa-cli/src/research/store/index.mjs +312 -0
- package/dist/engineering-pa-cli/src/research/store/manifest.mjs +56 -0
- package/dist/engineering-pa-cli/src/research/store/plan.mjs +59 -0
- package/dist/engineering-pa-cli/src/research/store/sources.mjs +44 -0
- package/dist/engineering-pa-cli/src/research/store/validate.mjs +92 -0
- package/dist/engineering-pa-cli/src/research/workflows/research-normal.js +573 -0
- package/dist/engineering-pa-cli/src/scheduler/cli.mjs +197 -0
- package/dist/engineering-pa-cli/src/secrets/cli.mjs +254 -0
- package/dist/engineering-pa-cli/src/secrets/commands/backup-template.mjs +67 -0
- package/dist/engineering-pa-cli/src/secrets/commands/backup.mjs +85 -0
- package/dist/engineering-pa-cli/src/secrets/commands/delete.mjs +58 -0
- package/dist/engineering-pa-cli/src/secrets/commands/find.mjs +36 -0
- package/dist/engineering-pa-cli/src/secrets/commands/get.mjs +49 -0
- package/dist/engineering-pa-cli/src/secrets/commands/init.mjs +69 -0
- package/dist/engineering-pa-cli/src/secrets/commands/list.mjs +37 -0
- package/dist/engineering-pa-cli/src/secrets/commands/rotate.mjs +46 -0
- package/dist/engineering-pa-cli/src/secrets/commands/set.mjs +123 -0
- package/dist/engineering-pa-cli/src/secrets/commands/status.mjs +146 -0
- package/dist/engineering-pa-cli/src/secrets/commands/vaults.mjs +57 -0
- package/dist/engineering-pa-cli/src/secrets/commands/verify.mjs +143 -0
- package/dist/engineering-pa-cli/src/secrets/vault.mjs +766 -0
- package/dist/engineering-pa-cli/src/smoke/checks.mjs +199 -0
- package/dist/engineering-pa-cli/src/smoke/cli.mjs +108 -0
- package/dist/engineering-pa-cli/src/smoke/commands/run.mjs +237 -0
- package/dist/engineering-pa-cli/src/smoke/runner.mjs +191 -0
- package/dist/engineering-pa-cli/src/staging-alias/cli.mjs +91 -0
- package/dist/engineering-pa-cli/src/staging-alias/cloudflare.mjs +380 -0
- package/dist/engineering-pa-cli/src/staging-alias/commands/list.mjs +36 -0
- package/dist/engineering-pa-cli/src/staging-alias/commands/set.mjs +188 -0
- package/dist/engineering-pa-cli/src/staging-alias/commands/status.mjs +113 -0
- package/dist/engineering-pa-cli/src/staging-alias/commands/unset.mjs +95 -0
- package/dist/engineering-pa-cli/src/staging-alias/sites.mjs +105 -0
- package/dist/engineering-pa-cli/src/sync/cli.mjs +151 -0
- package/dist/engineering-pa-cli/src/sync/git.mjs +149 -0
- package/dist/engineering-pa-cli/src/sync/lock.mjs +30 -0
- package/dist/engineering-pa-cli/src/sync/regenerated-artifacts.mjs +114 -0
- package/dist/engineering-pa-cli/src/sync/registry-scope.mjs +260 -0
- package/dist/engineering-pa-cli/src/sync/run.mjs +531 -0
- package/dist/engineering-pa-cli/src/sync/runner.mjs +62 -0
- package/dist/engineering-pa-cli/src/sync/runs-digest.mjs +138 -0
- package/dist/engineering-pa-core/README.md +60 -0
- package/dist/engineering-pa-core/bin/desk +9 -0
- package/dist/engineering-pa-core/bin/distill +2 -0
- package/dist/engineering-pa-core/bin/memory-sync +2 -0
- package/dist/engineering-pa-core/bin/slice +2 -0
- package/dist/engineering-pa-core/docs/autonomy-envelope-tracker.md +111 -0
- package/dist/engineering-pa-core/docs/blueprints/README.md +45 -0
- package/dist/engineering-pa-core/docs/blueprints/docker-services/blueprint.md +168 -0
- package/dist/engineering-pa-core/docs/blueprints/git-identity/blueprint.md +116 -0
- package/dist/engineering-pa-core/docs/blueprints/ideas/custom-workspace-views.md +9 -0
- package/dist/engineering-pa-core/docs/blueprints/ideas/knowledge-domain-patterns.md +15 -0
- package/dist/engineering-pa-core/docs/blueprints/ideas/office-integration.md +11 -0
- package/dist/engineering-pa-core/docs/blueprints/ideas/team-scaling.md +9 -0
- package/dist/engineering-pa-core/docs/blueprints/ideas/work-personal-separation.md +9 -0
- package/dist/engineering-pa-core/docs/blueprints/scheduled-distillation/blueprint.md +129 -0
- package/dist/engineering-pa-core/docs/blueprints/tool-integration/blueprint.md +110 -0
- package/dist/engineering-pa-core/docs/commands/init.md +159 -0
- package/dist/engineering-pa-core/docs/commands/schedule.md +98 -0
- package/dist/engineering-pa-core/docs/commands/upgrade.md +67 -0
- package/dist/engineering-pa-core/docs/entity-location-map.md +93 -0
- package/dist/engineering-pa-core/docs/knowledge-scaffold.md +121 -0
- package/dist/engineering-pa-core/docs/lock.md +155 -0
- package/dist/engineering-pa-core/docs/memory-sync.md +28 -0
- package/dist/engineering-pa-core/docs/pa-operating-model.md +457 -0
- package/dist/engineering-pa-core/docs/scheduling.md +35 -0
- package/dist/engineering-pa-core/docs/standards/README.md +24 -0
- package/dist/engineering-pa-core/docs/standards/briefing-contract.md +161 -0
- package/dist/engineering-pa-core/docs/standards/tooling.md +240 -0
- package/dist/engineering-pa-core/docs/tools/distill.md +230 -0
- package/dist/engineering-pa-core/docs/tools/memory-sync.md +98 -0
- package/dist/engineering-pa-core/docs/tools/scheduler.md +243 -0
- package/dist/engineering-pa-core/docs/tools/slice.md +150 -0
- package/dist/engineering-pa-core/docs/upstream-issue-filing.md +88 -0
- package/dist/engineering-pa-core/docs/working-directory.md +29 -0
- package/dist/engineering-pa-core/docs/workspace-operations.md +46 -0
- package/dist/engineering-pa-core/index.mjs +21 -0
- package/dist/engineering-pa-core/knowledge/README.md +53 -0
- package/dist/engineering-pa-core/knowledge/canonical/.gitkeep +0 -0
- package/dist/engineering-pa-core/knowledge/content-voice/README.md +64 -0
- package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-baseline.md +133 -0
- package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-shape.md +58 -0
- package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-taxonomy.md +33 -0
- package/dist/engineering-pa-core/knowledge/content-voice/content-pipeline-routing.md +58 -0
- package/dist/engineering-pa-core/knowledge/content-voice/new-tell-capture-procedure.md +40 -0
- package/dist/engineering-pa-core/knowledge/content-voice/voice-guide-shape.md +43 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/README.md +34 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/briefing-contract.md +77 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/delegation-modes.md +84 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/dispatch-envelope.md +238 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/task-groups-and-orchestrators.md +62 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/team-improvement-loop.md +48 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/tool-development-policy.md +48 -0
- package/dist/engineering-pa-core/knowledge/engineering-discipline/README.md +33 -0
- package/dist/engineering-pa-core/knowledge/engineering-discipline/blast-radius-and-safety.md +50 -0
- package/dist/engineering-pa-core/knowledge/engineering-discipline/investigate-root-causes.md +48 -0
- package/dist/engineering-pa-core/knowledge/engineering-discipline/role-ladder.md +57 -0
- package/dist/engineering-pa-core/knowledge/engineering-discipline/verify-runtime-behaviour.md +53 -0
- package/dist/engineering-pa-core/knowledge/engineering-discipline/worktree-isolation.md +50 -0
- package/dist/engineering-pa-core/knowledge/knowledge-memory/README.md +33 -0
- package/dist/engineering-pa-core/knowledge/knowledge-memory/claude-md-hygiene.md +47 -0
- package/dist/engineering-pa-core/knowledge/knowledge-memory/journal-and-self-improvement.md +53 -0
- package/dist/engineering-pa-core/knowledge/knowledge-memory/knowledge-retrieval-discipline.md +62 -0
- package/dist/engineering-pa-core/knowledge/knowledge-memory/librarian-synthesis-mandate.md +56 -0
- package/dist/engineering-pa-core/knowledge/knowledge-memory/staging-and-observations.md +58 -0
- package/dist/engineering-pa-core/knowledge/operator-interface/README.md +32 -0
- package/dist/engineering-pa-core/knowledge/operator-interface/communication-style.md +33 -0
- package/dist/engineering-pa-core/knowledge/operator-interface/escalation-and-correction.md +45 -0
- package/dist/engineering-pa-core/knowledge/operator-interface/push-back-discipline.md +42 -0
- package/dist/engineering-pa-core/knowledge/operator-interface/session-boot-and-end.md +56 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/README.md +37 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/desk-cli-reference.md +52 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/environment-awareness.md +91 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/mcp-servers-catalogue.md +52 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/pa-cli-reference.md +55 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/scheduling-and-launchd.md +56 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/tool-discovery-discipline.md +49 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/working-directory-vs-project-root.md +43 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/workspace-server.md +51 -0
- package/dist/engineering-pa-core/knowledge/raw/.gitkeep +0 -0
- package/dist/engineering-pa-core/knowledge/scaffold/domains.json +5 -0
- package/dist/engineering-pa-core/knowledge/schema/.gitkeep +0 -0
- package/dist/engineering-pa-core/knowledge/work-management/README.md +33 -0
- package/dist/engineering-pa-core/knowledge/work-management/desk-cli.md +84 -0
- package/dist/engineering-pa-core/knowledge/work-management/progress-md.md +48 -0
- package/dist/engineering-pa-core/knowledge/work-management/projects.md +64 -0
- package/dist/engineering-pa-core/knowledge/work-management/spawn-log.md +48 -0
- package/dist/engineering-pa-core/knowledge/work-management/work-items.md +67 -0
- package/dist/engineering-pa-core/package.json +26 -0
- package/dist/engineering-pa-core/roles/agent-ops/definition.md +79 -0
- package/dist/engineering-pa-core/roles/dispatch-briefer/definition.md +195 -0
- package/dist/engineering-pa-core/roles/librarian/definition.md +176 -0
- package/dist/engineering-pa-core/roles/pa/definition.md +93 -0
- package/dist/engineering-pa-core/roles/system-admin/definition.md +106 -0
- package/dist/engineering-pa-core/roles/tool-developer/definition.md +116 -0
- package/dist/engineering-pa-core/roles/writer/definition.md +130 -0
- package/dist/engineering-pa-core/schema/autonomy-envelope.schema.json +61 -0
- package/dist/engineering-pa-core/schema/project.json +114 -0
- package/dist/engineering-pa-core/schema/schedule.json +63 -0
- package/dist/engineering-pa-core/schema/work-item.json +92 -0
- package/dist/engineering-pa-core/src/config/pa-package-name.mjs +30 -0
- package/dist/engineering-pa-core/src/desk/cli.mjs +95 -0
- package/dist/engineering-pa-core/src/desk/commands.mjs +398 -0
- package/dist/engineering-pa-core/src/desk/format.mjs +84 -0
- package/dist/engineering-pa-core/src/desk/repo.mjs +172 -0
- package/dist/engineering-pa-core/src/desk/team-scope.mjs +142 -0
- package/dist/engineering-pa-core/src/distill/cli.mjs +112 -0
- package/dist/engineering-pa-core/src/distill/prereqs.mjs +171 -0
- package/dist/engineering-pa-core/src/distill/prompt.mjs +74 -0
- package/dist/engineering-pa-core/src/distill/run.mjs +230 -0
- package/dist/engineering-pa-core/src/distill/runner.mjs +86 -0
- package/dist/engineering-pa-core/src/distill/staging.mjs +59 -0
- package/dist/engineering-pa-core/src/knowledge/retrieval/backlink-indexer.mjs +154 -0
- package/dist/engineering-pa-core/src/knowledge/retrieval/graph-builder.mjs +186 -0
- package/dist/engineering-pa-core/src/knowledge/retrieval/index-builder.mjs +225 -0
- package/dist/engineering-pa-core/src/knowledge/retrieval/query.mjs +263 -0
- package/dist/engineering-pa-core/src/lock/lock.mjs +202 -0
- package/dist/engineering-pa-core/src/memory-sync/cli-support.mjs +12 -0
- package/dist/engineering-pa-core/src/memory-sync/cli.mjs +56 -0
- package/dist/engineering-pa-core/src/memory-sync/commands/pull.mjs +44 -0
- package/dist/engineering-pa-core/src/memory-sync/commands/push.mjs +46 -0
- package/dist/engineering-pa-core/src/memory-sync/mirror.mjs +114 -0
- package/dist/engineering-pa-core/src/memory-sync/repo.mjs +65 -0
- package/dist/engineering-pa-core/src/scheduler/detect.mjs +111 -0
- package/dist/engineering-pa-core/src/scheduler/host.mjs +38 -0
- package/dist/engineering-pa-core/src/scheduler/index.mjs +13 -0
- package/dist/engineering-pa-core/src/scheduler/install.mjs +854 -0
- package/dist/engineering-pa-core/src/scheduler/list.mjs +168 -0
- package/dist/engineering-pa-core/src/scheduler/remove.mjs +176 -0
- package/dist/engineering-pa-core/src/scheduler/verify.mjs +435 -0
- package/dist/engineering-pa-core/src/session-boot/autonomy-injection.mjs +84 -0
- package/dist/engineering-pa-core/src/slice/cli.mjs +139 -0
- package/dist/engineering-pa-core/src/slice/inputs.mjs +174 -0
- package/dist/engineering-pa-core/src/slice/prompt.mjs +94 -0
- package/dist/engineering-pa-core/src/slice/prompts/dave-boot.md +91 -0
- package/dist/engineering-pa-core/src/slice/run.mjs +334 -0
- package/dist/engineering-pa-core-extensions-atlassian/README.md +31 -0
- package/dist/engineering-pa-core-extensions-atlassian/index.mjs +9 -0
- package/dist/engineering-pa-core-extensions-atlassian/package.json +19 -0
- package/dist/engineering-pa-core-extensions-n8n/README.md +29 -0
- package/dist/engineering-pa-core-extensions-n8n/index.mjs +8 -0
- package/dist/engineering-pa-core-extensions-n8n/package.json +19 -0
- package/dist/engineering-pa-librarian/README.md +38 -0
- package/dist/engineering-pa-librarian/index.mjs +12 -0
- package/dist/engineering-pa-librarian/package.json +19 -0
- package/dist/engineering-pa-migrate-inspect/README.md +131 -0
- package/dist/engineering-pa-migrate-inspect/bin/migrate-inspect +2 -0
- package/dist/engineering-pa-migrate-inspect/package.json +21 -0
- package/dist/engineering-pa-migrate-inspect/src/age-key-migrate.mjs +83 -0
- package/dist/engineering-pa-migrate-inspect/src/cli.mjs +97 -0
- package/dist/engineering-pa-migrate-inspect/src/inspector.mjs +237 -0
- package/dist/engineering-pa-migrate-inspect/src/platforms/linux.mjs +87 -0
- package/dist/engineering-pa-migrate-inspect/src/platforms/macos.mjs +105 -0
- package/dist/engineering-pa-migrate-inspect/src/runner.mjs +87 -0
- package/dist/engineering-pa-runbook/README.md +103 -0
- package/dist/engineering-pa-runbook/package.json +18 -0
- package/dist/engineering-pa-runbook/reference/dex-template.runbook.json +66 -0
- package/dist/engineering-pa-runbook/reference/hq-dave.runbook.json +69 -0
- package/dist/engineering-pa-runbook/reference/smarthome01.runbook.json +92 -0
- package/dist/engineering-pa-runbook/schema/inspection.schema.json +113 -0
- package/dist/engineering-pa-runbook/schema/runbook.schema.json +67 -0
- package/dist/engineering-pa-runbook/src/validator.mjs +167 -0
- package/dist/engineering-pa-scaffold/README.md +164 -0
- package/dist/engineering-pa-scaffold/bin/pa-init +2 -0
- package/dist/engineering-pa-scaffold/index.mjs +42 -0
- package/dist/engineering-pa-scaffold/package.json +22 -0
- package/dist/engineering-pa-scaffold/src/cli.mjs +115 -0
- package/dist/engineering-pa-scaffold/src/commands/init.mjs +169 -0
- package/dist/engineering-pa-scaffold/src/generator.mjs +354 -0
- package/dist/engineering-pa-scaffold/src/prompts.mjs +125 -0
- package/dist/engineering-pa-scaffold/src/runner.mjs +142 -0
- package/dist/engineering-pa-scaffold/templates/CLAUDE.md.template +196 -0
- package/dist/engineering-pa-scaffold/templates/operator-README.md.template +44 -0
- package/dist/engineering-pa-types-engineering/README.md +35 -0
- package/dist/engineering-pa-types-engineering/docs/commands/product-build.md +120 -0
- package/dist/engineering-pa-types-engineering/index.mjs +9 -0
- package/dist/engineering-pa-types-engineering/package.json +19 -0
- package/dist/engineering-pa-types-engineering/roles/product-engineer/definition.md +650 -0
- package/dist/engineering-pa-types-engineering/roles/product-engineer/templates/build-spec.md +82 -0
- package/dist/engineering-pa-types-engineering/roles/product-engineer/templates/preflight-checklist.md +158 -0
- package/dist/engineering-pa-types-engineering/roles/product-engineer/templates/worker-brief.md +130 -0
- package/dist/engineering-pa-workspace/README.md +197 -0
- package/dist/engineering-pa-workspace/app/assets/md.js +187 -0
- package/dist/engineering-pa-workspace/app/assets/modal.js +120 -0
- package/dist/engineering-pa-workspace/app/assets/theme.css +132 -0
- package/dist/engineering-pa-workspace/app/assets/theme.js +159 -0
- package/dist/engineering-pa-workspace/app/banner.js +320 -0
- package/dist/engineering-pa-workspace/app/delegations.html +345 -0
- package/dist/engineering-pa-workspace/app/home.html +1060 -0
- package/dist/engineering-pa-workspace/app/index.html +361 -0
- package/dist/engineering-pa-workspace/app/knowledge-map.html +163 -0
- package/dist/engineering-pa-workspace/app/knowledge.html +304 -0
- package/dist/engineering-pa-workspace/app/nav.json +75 -0
- package/dist/engineering-pa-workspace/app/notes.html +199 -0
- package/dist/engineering-pa-workspace/app/onboarding.html +592 -0
- package/dist/engineering-pa-workspace/app/profile.html +243 -0
- package/dist/engineering-pa-workspace/app/project-detail.html +432 -0
- package/dist/engineering-pa-workspace/app/projects.html +297 -0
- package/dist/engineering-pa-workspace/app/repos.html +598 -0
- package/dist/engineering-pa-workspace/app/schedules.html +486 -0
- package/dist/engineering-pa-workspace/app/secrets.html +164 -0
- package/dist/engineering-pa-workspace/app/staging.html +319 -0
- package/dist/engineering-pa-workspace/app/tasks.html +465 -0
- package/dist/engineering-pa-workspace/app/vendor/chart.umd.min.js +21 -0
- package/dist/engineering-pa-workspace/app/viewer.html +116 -0
- package/dist/engineering-pa-workspace/docs/DAEMON.md +104 -0
- package/dist/engineering-pa-workspace/docs/page-blueprint.md +286 -0
- package/dist/engineering-pa-workspace/docs/status-mon.md +168 -0
- package/dist/engineering-pa-workspace/index.mjs +12 -0
- package/dist/engineering-pa-workspace/nav.json.example +8 -0
- package/dist/engineering-pa-workspace/package.json +27 -0
- package/dist/engineering-pa-workspace/src/api.mjs +2086 -0
- package/dist/engineering-pa-workspace/src/install-daemon.mjs +573 -0
- package/dist/engineering-pa-workspace/src/nav.mjs +190 -0
- package/dist/engineering-pa-workspace/src/secrets-api.mjs +175 -0
- package/dist/engineering-pa-workspace/src/server.mjs +516 -0
- package/dist/engineering-pa-workspace/src/status-mon/poll.mjs +220 -0
- package/dist/engineering-pa-workspace/src/status-mon/poller.mjs +113 -0
- package/dist/engineering-pa-workspace/src/status-mon/run.mjs +133 -0
- package/dist/engineering-pa-workspace/src/supervisor.mjs +264 -0
- package/dist/engineering-pa-workspace/src/ws.mjs +633 -0
- package/index.mjs +60 -0
- package/package.json +58 -0
- package/templates/banned-ai-tells.md +102 -0
- package/templates/purpose.md +105 -0
- package/templates/voice-guide.md +98 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// task-loader.mjs — pure: load + validate a task definition from schedules/.
|
|
2
|
+
// Task path: <repoRoot>/schedules/<id>.json
|
|
3
|
+
// Required fields: id, command
|
|
4
|
+
import { readFile } from 'node:fs/promises';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
const REQUIRED_FIELDS = ['id', 'command'];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Reject task commands that contain shell quoting. The runner spawns
|
|
11
|
+
* commands directly (no shell), so quotes are not honoured — they would
|
|
12
|
+
* silently shred the args (e.g. `bash -c "a && b"` → spawn('bash',
|
|
13
|
+
* ['-c', '"a', '&&', 'b"']), bash exits 2 with "unexpected EOF").
|
|
14
|
+
*
|
|
15
|
+
* Validated at load time so --dry-run also catches the bad shape, and
|
|
16
|
+
* re-asserted by runner.parseCommand as a chokepoint guarantee for
|
|
17
|
+
* programmatic callers.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} command
|
|
20
|
+
* @throws {Error & { exitCode: 2 }} if command is not a non-empty string,
|
|
21
|
+
* or contains a single (') or double (") quote character
|
|
22
|
+
*/
|
|
23
|
+
export function assertNoShellQuoting(command) {
|
|
24
|
+
if (typeof command !== 'string') {
|
|
25
|
+
const e = new Error('command must be a string');
|
|
26
|
+
e.exitCode = 2;
|
|
27
|
+
throw e;
|
|
28
|
+
}
|
|
29
|
+
const m = command.match(/['"]/);
|
|
30
|
+
if (m) {
|
|
31
|
+
const e = new Error(
|
|
32
|
+
`command contains shell quoting (${JSON.stringify(m[0])} at index ${m.index}) — ` +
|
|
33
|
+
'the runner spawns commands directly without a shell, so quotes are not honoured ' +
|
|
34
|
+
'and would silently shred the args. If you need shell features (variable expansion, ' +
|
|
35
|
+
'&&, pipes, sourcing), wrap the command in a script file and invoke the script, ' +
|
|
36
|
+
'or move the shell wrapper to the cron entry instead of the task command.'
|
|
37
|
+
);
|
|
38
|
+
e.exitCode = 2;
|
|
39
|
+
throw e;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function loadTask(repoRoot, taskId) {
|
|
44
|
+
const path = join(repoRoot, 'schedules', `${taskId}.json`);
|
|
45
|
+
let raw;
|
|
46
|
+
try {
|
|
47
|
+
raw = await readFile(path, 'utf8');
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (err.code === 'ENOENT') {
|
|
50
|
+
const e = new Error(`task "${taskId}" not found in schedules (${path})`);
|
|
51
|
+
e.exitCode = 2;
|
|
52
|
+
throw e;
|
|
53
|
+
}
|
|
54
|
+
const e = new Error(`could not read schedule file for "${taskId}": ${err.message}`);
|
|
55
|
+
e.exitCode = 3;
|
|
56
|
+
throw e;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let data;
|
|
60
|
+
try {
|
|
61
|
+
data = JSON.parse(raw);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
const e = new Error(`schedule file for "${taskId}" is not valid JSON: ${err.message}`);
|
|
64
|
+
e.exitCode = 2;
|
|
65
|
+
throw e;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const field of REQUIRED_FIELDS) {
|
|
69
|
+
if (!data[field]) {
|
|
70
|
+
const e = new Error(`task "${taskId}": missing required field "${field}"`);
|
|
71
|
+
e.exitCode = 2;
|
|
72
|
+
throw e;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
assertNoShellQuoting(data.command);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
const e = new Error(`task "${taskId}": ${err.message}`);
|
|
80
|
+
e.exitCode = err.exitCode ?? 2;
|
|
81
|
+
throw e;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return data;
|
|
85
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// lint — voice/format linter subcommand router.
|
|
2
|
+
//
|
|
3
|
+
// Wired as `pa lint <verb>` (verb-second pattern, mirrors `pa gsc`). Today the
|
|
4
|
+
// sole verb is `linkedin`; the router exists so other surfaces (article,
|
|
5
|
+
// newsletter) can land as sibling verbs without reshaping the dispatch.
|
|
6
|
+
//
|
|
7
|
+
// Exit codes are lint-semantic and owned by each command (0 clean / 1 errors /
|
|
8
|
+
// 2 warnings / 3 usage|IO|config) — see docs/lint-linkedin.md. The router
|
|
9
|
+
// itself returns 3 for its own usage errors (no verb, unknown verb).
|
|
10
|
+
|
|
11
|
+
import { run as runLinkedin } from './commands/linkedin.mjs';
|
|
12
|
+
|
|
13
|
+
const HELP = `pa lint — lint operator-authored text against voice rules
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
pa lint linkedin <input-file> [--rules <path>] [--format <text|json>]
|
|
17
|
+
[--severity <error|warning|info>] [--surface <name>]
|
|
18
|
+
Lint a file against the LinkedIn voice-rule corpus and report
|
|
19
|
+
violations in alter-format (swap → / cut → / rename →).
|
|
20
|
+
|
|
21
|
+
pa lint -h, --help
|
|
22
|
+
Show this help.
|
|
23
|
+
|
|
24
|
+
Exit codes (lint-semantic, ESLint-style — see docs/lint-linkedin.md):
|
|
25
|
+
0 clean | 1 errors found | 2 warnings only | 3 usage / IO / config error
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
export async function run(argv, overrides = {}) {
|
|
29
|
+
const verb = argv[0];
|
|
30
|
+
const rest = argv.slice(1);
|
|
31
|
+
|
|
32
|
+
if (!verb || verb === '--help' || verb === '-h' || verb === 'help') {
|
|
33
|
+
(overrides.stdout ?? process.stdout).write(HELP);
|
|
34
|
+
// No verb is a usage error (3); explicit help is success (0).
|
|
35
|
+
return verb ? 0 : 3;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
switch (verb) {
|
|
39
|
+
case 'linkedin':
|
|
40
|
+
return runLinkedin(rest, overrides);
|
|
41
|
+
default:
|
|
42
|
+
(overrides.stderr ?? process.stderr).write(`[pa lint] unknown verb: ${verb}\n`);
|
|
43
|
+
(overrides.stdout ?? process.stdout).write(HELP);
|
|
44
|
+
return 3;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// CLI entry: map thrown errors to exit codes (err.exitCode or 3 for the
|
|
49
|
+
// lint family — every non-finding failure here is usage/IO/config).
|
|
50
|
+
export async function main(argv, overrides = {}) {
|
|
51
|
+
try {
|
|
52
|
+
const code = await run(argv, overrides);
|
|
53
|
+
return code ?? 0;
|
|
54
|
+
} catch (err) {
|
|
55
|
+
const code = err && typeof err.exitCode === 'number' ? err.exitCode : 3;
|
|
56
|
+
(overrides.stderr ?? process.stderr).write(
|
|
57
|
+
`pa lint: ${err && err.message ? err.message : err}\n`,
|
|
58
|
+
);
|
|
59
|
+
return code;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// linkedin.mjs — `pa lint linkedin <file>` command.
|
|
2
|
+
//
|
|
3
|
+
// Loads the voice-rule corpus, lints the input file, prints violations in
|
|
4
|
+
// alter-format, and returns a lint-semantic exit code.
|
|
5
|
+
//
|
|
6
|
+
// Exit codes for this command (deliberately lint-semantic, ESLint-style —
|
|
7
|
+
// see docs/lint-linkedin.md for why this differs from tooling standard §6):
|
|
8
|
+
// 0 clean (or only info / below floor) | 1 errors found |
|
|
9
|
+
// 2 warnings only | 3 usage / IO / config error
|
|
10
|
+
|
|
11
|
+
import { readFile } from 'node:fs/promises';
|
|
12
|
+
import { existsSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { getRepoRoot } from '../../pa/repo-root.mjs';
|
|
15
|
+
import { loadRules } from '../rules.mjs';
|
|
16
|
+
import { lint, applySeverityFloor, exitCodeFor } from '../linter.mjs';
|
|
17
|
+
import { formatText, formatJson } from '../reporter.mjs';
|
|
18
|
+
|
|
19
|
+
const DEFAULT_RULES_REL = 'operator/knowledge/writing/linkedin-rules.json';
|
|
20
|
+
const FORMATS = new Set(['text', 'json']);
|
|
21
|
+
const SEVERITIES = new Set(['error', 'warning', 'info']);
|
|
22
|
+
|
|
23
|
+
export const USAGE = `pa lint linkedin — lint text against the LinkedIn voice rules
|
|
24
|
+
|
|
25
|
+
Usage:
|
|
26
|
+
pa lint linkedin <input-file> [options]
|
|
27
|
+
|
|
28
|
+
Options:
|
|
29
|
+
--rules <path> Rule corpus JSON. Defaults to
|
|
30
|
+
${DEFAULT_RULES_REL} relative to the operator repo
|
|
31
|
+
root; required if that file is absent.
|
|
32
|
+
--format <text|json> Output format (default: text).
|
|
33
|
+
--severity <error|warning|info>
|
|
34
|
+
Severity floor — only violations at or above this
|
|
35
|
+
level are reported (default: error).
|
|
36
|
+
--surface <name> Surface context: all | linkedin | article |
|
|
37
|
+
newsletter | reply (default: linkedin). Rules whose
|
|
38
|
+
surface is "all" or includes this value apply.
|
|
39
|
+
-h, --help Show this help.
|
|
40
|
+
|
|
41
|
+
Output (text): one line per violation —
|
|
42
|
+
<file>:<line>:<col> <severity> <ruleId> <matched> → <alter-guidance>
|
|
43
|
+
|
|
44
|
+
Exit codes (lint-semantic):
|
|
45
|
+
0 clean | 1 errors found | 2 warnings only | 3 usage / IO / config error
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
function usageError(message) {
|
|
49
|
+
return Object.assign(new Error(message), { exitCode: 3 });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Minimal flag parser: one positional (the input file) plus the long flags
|
|
53
|
+
// above. Unknown flags and missing flag values are usage errors.
|
|
54
|
+
export function parseArgs(argv) {
|
|
55
|
+
const opts = { file: null, rules: null, format: 'text', severity: 'error', surface: 'linkedin' };
|
|
56
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
57
|
+
const arg = argv[i];
|
|
58
|
+
const takeValue = (name) => {
|
|
59
|
+
const v = argv[i + 1];
|
|
60
|
+
if (v === undefined) throw usageError(`${name} requires a value`);
|
|
61
|
+
i += 1;
|
|
62
|
+
return v;
|
|
63
|
+
};
|
|
64
|
+
switch (arg) {
|
|
65
|
+
case '--rules': opts.rules = takeValue('--rules'); break;
|
|
66
|
+
case '--format': opts.format = takeValue('--format'); break;
|
|
67
|
+
case '--severity': opts.severity = takeValue('--severity'); break;
|
|
68
|
+
case '--surface': opts.surface = takeValue('--surface'); break;
|
|
69
|
+
default:
|
|
70
|
+
if (arg.startsWith('-')) throw usageError(`unknown flag: ${arg}`);
|
|
71
|
+
if (opts.file !== null) throw usageError(`unexpected extra argument: ${arg}`);
|
|
72
|
+
opts.file = arg;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!opts.file) throw usageError('missing <input-file>');
|
|
76
|
+
if (!FORMATS.has(opts.format)) throw usageError(`invalid --format "${opts.format}" (text|json)`);
|
|
77
|
+
if (!SEVERITIES.has(opts.severity)) {
|
|
78
|
+
throw usageError(`invalid --severity "${opts.severity}" (error|warning|info)`);
|
|
79
|
+
}
|
|
80
|
+
return opts;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Resolve the rules path: explicit --rules wins; otherwise the default under
|
|
84
|
+
// the operator repo root, if that file exists. No default and no flag is a
|
|
85
|
+
// usage error with an actionable message.
|
|
86
|
+
async function resolveRulesPath(explicit) {
|
|
87
|
+
if (explicit) {
|
|
88
|
+
if (!existsSync(explicit)) throw usageError(`rules file not found: ${explicit}`);
|
|
89
|
+
return explicit;
|
|
90
|
+
}
|
|
91
|
+
let root;
|
|
92
|
+
try {
|
|
93
|
+
root = await getRepoRoot();
|
|
94
|
+
} catch {
|
|
95
|
+
root = null;
|
|
96
|
+
}
|
|
97
|
+
if (root) {
|
|
98
|
+
const candidate = join(root, DEFAULT_RULES_REL);
|
|
99
|
+
if (existsSync(candidate)) return candidate;
|
|
100
|
+
}
|
|
101
|
+
throw usageError(
|
|
102
|
+
`no --rules given and default corpus not found (expected ${DEFAULT_RULES_REL} under the repo root); pass --rules <path>`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function run(argv, overrides = {}) {
|
|
107
|
+
const out = overrides.stdout ?? process.stdout;
|
|
108
|
+
if (argv[0] === '-h' || argv[0] === '--help') {
|
|
109
|
+
out.write(USAGE);
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
const opts = parseArgs(argv);
|
|
113
|
+
|
|
114
|
+
const rulesPath = await resolveRulesPath(opts.rules);
|
|
115
|
+
const rules = await loadRules(rulesPath);
|
|
116
|
+
|
|
117
|
+
let text;
|
|
118
|
+
try {
|
|
119
|
+
text = await readFile(opts.file, 'utf8');
|
|
120
|
+
} catch (e) {
|
|
121
|
+
throw usageError(`cannot read input file ${opts.file}: ${e.message}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const all = lint(text, rules, { surface: opts.surface });
|
|
125
|
+
const reported = applySeverityFloor(all, opts.severity);
|
|
126
|
+
|
|
127
|
+
if (opts.format === 'json') {
|
|
128
|
+
out.write(formatJson(reported, opts.file) + '\n');
|
|
129
|
+
} else if (reported.length > 0) {
|
|
130
|
+
out.write(formatText(reported, opts.file) + '\n');
|
|
131
|
+
}
|
|
132
|
+
return exitCodeFor(reported);
|
|
133
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// detectors.mjs — the three detector types for the linkedin voice linter.
|
|
2
|
+
//
|
|
3
|
+
// A detector takes the input text and a rule's `detect` block and returns an
|
|
4
|
+
// array of matches: { line, col, matched }. Lines and columns are 1-based so
|
|
5
|
+
// the text reporter can emit clickable `file:line:col` pointers.
|
|
6
|
+
//
|
|
7
|
+
// `literal-phrase` and `regex` scan line-by-line so positions fall out
|
|
8
|
+
// naturally and a regex's negated character classes (e.g. `[^.,;]`) cannot
|
|
9
|
+
// silently swallow newlines. `count` is document-wide: it flags every match
|
|
10
|
+
// beyond `max` so the writer sees each excess occurrence, not just the count.
|
|
11
|
+
|
|
12
|
+
const WORD = /\w/;
|
|
13
|
+
|
|
14
|
+
// Escape a literal string for safe embedding in a RegExp source.
|
|
15
|
+
function escapeRegex(s) {
|
|
16
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Build the RegExp used by a `literal-phrase` detector.
|
|
20
|
+
//
|
|
21
|
+
// Word boundaries are applied conditionally: a leading `\b` only when the
|
|
22
|
+
// phrase starts with a word character, a trailing `\b` only when it ends with
|
|
23
|
+
// one. A blanket `\b...\b` around a phrase like "it's worth noting" is fine,
|
|
24
|
+
// but around punctuation-edged phrases it would never match — conditional
|
|
25
|
+
// anchoring keeps boundaries meaningful without false negatives.
|
|
26
|
+
function buildPhraseRegex(detect) {
|
|
27
|
+
const phrase = detect.phrase ?? '';
|
|
28
|
+
const wordBoundary = detect.wordBoundary !== false; // default true
|
|
29
|
+
const caseSensitive = detect.caseSensitive === true; // default false
|
|
30
|
+
let src = escapeRegex(phrase);
|
|
31
|
+
if (wordBoundary) {
|
|
32
|
+
if (WORD.test(phrase[0] ?? '')) src = '\\b' + src;
|
|
33
|
+
if (WORD.test(phrase[phrase.length - 1] ?? '')) src = src + '\\b';
|
|
34
|
+
}
|
|
35
|
+
return new RegExp(src, caseSensitive ? 'g' : 'gi');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildRegex(detect) {
|
|
39
|
+
const caseSensitive = detect.caseSensitive === true; // default false
|
|
40
|
+
return new RegExp(detect.pattern, caseSensitive ? 'g' : 'gi');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Scan one line for every non-overlapping match of `re` (which must carry the
|
|
44
|
+
// global flag). Returns [{ col, matched }] with 1-based columns.
|
|
45
|
+
function scanLine(line, re) {
|
|
46
|
+
const out = [];
|
|
47
|
+
re.lastIndex = 0;
|
|
48
|
+
let m;
|
|
49
|
+
while ((m = re.exec(line)) !== null) {
|
|
50
|
+
out.push({ col: m.index + 1, matched: m[0] });
|
|
51
|
+
// Guard against zero-width matches looping forever.
|
|
52
|
+
if (m.index === re.lastIndex) re.lastIndex += 1;
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Walk the document line-by-line, applying `re` to each line.
|
|
58
|
+
function scanDocument(text, re) {
|
|
59
|
+
const out = [];
|
|
60
|
+
const lines = text.split('\n');
|
|
61
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
62
|
+
for (const hit of scanLine(lines[i], re)) {
|
|
63
|
+
out.push({ line: i + 1, col: hit.col, matched: hit.matched });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Dispatch a rule's detect block against the text.
|
|
70
|
+
export function detect(text, detectBlock) {
|
|
71
|
+
switch (detectBlock.type) {
|
|
72
|
+
case 'literal-phrase':
|
|
73
|
+
return scanDocument(text, buildPhraseRegex(detectBlock));
|
|
74
|
+
case 'regex':
|
|
75
|
+
return scanDocument(text, buildRegex(detectBlock));
|
|
76
|
+
case 'count': {
|
|
77
|
+
const re = buildRegex({ pattern: detectBlock.pattern, caseSensitive: detectBlock.caseSensitive });
|
|
78
|
+
const all = scanDocument(text, re);
|
|
79
|
+
const max = Number.isFinite(detectBlock.max) ? detectBlock.max : 0;
|
|
80
|
+
// Pass if matches <= max. Otherwise flag every occurrence past the
|
|
81
|
+
// ceiling (the (max+1)th onward) so each excess use is actionable.
|
|
82
|
+
return all.length > max ? all.slice(max) : [];
|
|
83
|
+
}
|
|
84
|
+
default:
|
|
85
|
+
// Unreachable when rules are validated upstream, but be defensive.
|
|
86
|
+
throw Object.assign(new Error(`unknown detect type: ${detectBlock.type}`), { exitCode: 3 });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { escapeRegex, buildPhraseRegex, buildRegex, scanLine, scanDocument };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// linter.mjs — run a validated rule corpus over an input text.
|
|
2
|
+
//
|
|
3
|
+
// Pure engine: no IO, no process exit. Takes text + rules + options, returns
|
|
4
|
+
// a flat list of violations sorted by position. The CLI layer handles file
|
|
5
|
+
// reading, severity-floor filtering, formatting, and exit codes.
|
|
6
|
+
|
|
7
|
+
import { detect } from './detectors.mjs';
|
|
8
|
+
import { ruleAppliesToSurface } from './rules.mjs';
|
|
9
|
+
|
|
10
|
+
const SEVERITY_RANK = { info: 0, warning: 1, error: 2 };
|
|
11
|
+
|
|
12
|
+
// Render a rule's alter block into a single guidance string in alter-format:
|
|
13
|
+
// swap -> "swap → "use" | "X"" (the candidate replacements, then why)
|
|
14
|
+
// cut -> "cut → <guidance>"
|
|
15
|
+
// rename -> "rename → <guidance>"
|
|
16
|
+
export function formatAlter(alter) {
|
|
17
|
+
if (alter.kind === 'swap') {
|
|
18
|
+
const opts = alter.with.map((w) => JSON.stringify(w)).join(' | ');
|
|
19
|
+
return `swap → ${opts} — ${alter.guidance}`;
|
|
20
|
+
}
|
|
21
|
+
if (alter.kind === 'cut') return `cut → ${alter.guidance}`;
|
|
22
|
+
return `rename → ${alter.guidance}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Lint `text` against `rules` for the given surface. Returns every violation
|
|
26
|
+
// (unfiltered by severity); ordering is by line then column then ruleId so
|
|
27
|
+
// output is deterministic.
|
|
28
|
+
export function lint(text, rules, { surface = 'linkedin' } = {}) {
|
|
29
|
+
const violations = [];
|
|
30
|
+
for (const rule of rules) {
|
|
31
|
+
if (!ruleAppliesToSurface(rule, surface)) continue;
|
|
32
|
+
for (const hit of detect(text, rule.detect)) {
|
|
33
|
+
violations.push({
|
|
34
|
+
line: hit.line,
|
|
35
|
+
col: hit.col,
|
|
36
|
+
ruleId: rule.ruleId,
|
|
37
|
+
category: rule.category,
|
|
38
|
+
severity: rule.severity,
|
|
39
|
+
matched: hit.matched,
|
|
40
|
+
alter: formatAlter(rule.alter),
|
|
41
|
+
alterKind: rule.alter.kind,
|
|
42
|
+
rationale: rule.rationale,
|
|
43
|
+
source: rule.source,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
violations.sort((a, b) =>
|
|
48
|
+
a.line - b.line || a.col - b.col || a.ruleId.localeCompare(b.ruleId),
|
|
49
|
+
);
|
|
50
|
+
return violations;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Keep only violations at or above the severity floor.
|
|
54
|
+
export function applySeverityFloor(violations, floor) {
|
|
55
|
+
const min = SEVERITY_RANK[floor] ?? SEVERITY_RANK.error;
|
|
56
|
+
return violations.filter((v) => SEVERITY_RANK[v.severity] >= min);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Exit code from the reported (post-floor) violations, per the lint contract:
|
|
60
|
+
// 1 = at least one error 2 = warnings (no errors) 0 = info-only or clean
|
|
61
|
+
export function exitCodeFor(violations) {
|
|
62
|
+
let worst = -1;
|
|
63
|
+
for (const v of violations) worst = Math.max(worst, SEVERITY_RANK[v.severity]);
|
|
64
|
+
if (worst === SEVERITY_RANK.error) return 1;
|
|
65
|
+
if (worst === SEVERITY_RANK.warning) return 2;
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { SEVERITY_RANK };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// reporter.mjs — render violations as text or JSON.
|
|
2
|
+
//
|
|
3
|
+
// text: one line per violation —
|
|
4
|
+
// <file>:<line>:<col> <severity> <ruleId> <matched> → <alter-guidance>
|
|
5
|
+
// json: a machine-readable array, each entry
|
|
6
|
+
// { file, line, col, ruleId, severity, matched, alter, rationale, source }
|
|
7
|
+
|
|
8
|
+
export function formatText(violations, file) {
|
|
9
|
+
return violations
|
|
10
|
+
.map((v) =>
|
|
11
|
+
`${file}:${v.line}:${v.col} ${v.severity} ${v.ruleId} ${JSON.stringify(v.matched)} → ${v.alter}`,
|
|
12
|
+
)
|
|
13
|
+
.join('\n');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function toJsonShape(violations, file) {
|
|
17
|
+
return violations.map((v) => ({
|
|
18
|
+
file,
|
|
19
|
+
line: v.line,
|
|
20
|
+
col: v.col,
|
|
21
|
+
ruleId: v.ruleId,
|
|
22
|
+
severity: v.severity,
|
|
23
|
+
matched: v.matched,
|
|
24
|
+
alter: v.alter,
|
|
25
|
+
rationale: v.rationale,
|
|
26
|
+
source: v.source,
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function formatJson(violations, file) {
|
|
31
|
+
return JSON.stringify(toJsonShape(violations, file), null, 2);
|
|
32
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// rules.mjs — load and validate the linkedin voice-rule corpus.
|
|
2
|
+
//
|
|
3
|
+
// The corpus is a JSON array of rule objects. The schema is the contract
|
|
4
|
+
// shared with the production corpus authored at
|
|
5
|
+
// operator/knowledge/writing/linkedin-rules.json. Validation is strict: a
|
|
6
|
+
// malformed corpus is a configuration error (exit 3), not a lint finding —
|
|
7
|
+
// we refuse to run rather than silently skip broken rules.
|
|
8
|
+
|
|
9
|
+
import { readFile } from 'node:fs/promises';
|
|
10
|
+
|
|
11
|
+
const CATEGORIES = new Set([
|
|
12
|
+
'punctuation', 'vocabulary', 'phrase', 'reaction-phrase',
|
|
13
|
+
'filler', 'structural', 'format', 'count',
|
|
14
|
+
]);
|
|
15
|
+
const SEVERITIES = new Set(['error', 'warning', 'info']);
|
|
16
|
+
const SURFACES = new Set(['all', 'linkedin', 'article', 'newsletter', 'reply']);
|
|
17
|
+
const DETECT_TYPES = new Set(['literal-phrase', 'regex', 'count']);
|
|
18
|
+
const ALTER_KINDS = new Set(['swap', 'cut', 'rename']);
|
|
19
|
+
|
|
20
|
+
function configError(message) {
|
|
21
|
+
return Object.assign(new Error(message), { exitCode: 3 });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Validate one rule, throwing a configError describing the first problem.
|
|
25
|
+
// `where` locates the rule in the corpus for the operator.
|
|
26
|
+
function validateRule(rule, where) {
|
|
27
|
+
if (rule === null || typeof rule !== 'object' || Array.isArray(rule)) {
|
|
28
|
+
throw configError(`${where}: rule must be an object`);
|
|
29
|
+
}
|
|
30
|
+
const id = typeof rule.ruleId === 'string' ? rule.ruleId : null;
|
|
31
|
+
if (!id) throw configError(`${where}: missing or non-string ruleId`);
|
|
32
|
+
const at = `rule "${id}"`;
|
|
33
|
+
|
|
34
|
+
if (!CATEGORIES.has(rule.category)) {
|
|
35
|
+
throw configError(`${at}: invalid category "${rule.category}"`);
|
|
36
|
+
}
|
|
37
|
+
if (!Array.isArray(rule.surface) || rule.surface.length === 0) {
|
|
38
|
+
throw configError(`${at}: surface must be a non-empty array`);
|
|
39
|
+
}
|
|
40
|
+
for (const s of rule.surface) {
|
|
41
|
+
if (!SURFACES.has(s)) throw configError(`${at}: invalid surface "${s}"`);
|
|
42
|
+
}
|
|
43
|
+
if (!SEVERITIES.has(rule.severity)) {
|
|
44
|
+
throw configError(`${at}: invalid severity "${rule.severity}"`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const d = rule.detect;
|
|
48
|
+
if (!d || typeof d !== 'object') throw configError(`${at}: missing detect block`);
|
|
49
|
+
if (!DETECT_TYPES.has(d.type)) throw configError(`${at}: invalid detect.type "${d.type}"`);
|
|
50
|
+
if (d.type === 'literal-phrase') {
|
|
51
|
+
if (typeof d.phrase !== 'string' || d.phrase.length === 0) {
|
|
52
|
+
throw configError(`${at}: literal-phrase detect needs a non-empty phrase`);
|
|
53
|
+
}
|
|
54
|
+
} else if (d.type === 'regex') {
|
|
55
|
+
if (typeof d.pattern !== 'string' || d.pattern.length === 0) {
|
|
56
|
+
throw configError(`${at}: regex detect needs a pattern`);
|
|
57
|
+
}
|
|
58
|
+
assertCompiles(d.pattern, at);
|
|
59
|
+
} else if (d.type === 'count') {
|
|
60
|
+
if (typeof d.pattern !== 'string' || d.pattern.length === 0) {
|
|
61
|
+
throw configError(`${at}: count detect needs a pattern`);
|
|
62
|
+
}
|
|
63
|
+
if (typeof d.max !== 'number' || !Number.isFinite(d.max) || d.max < 0) {
|
|
64
|
+
throw configError(`${at}: count detect needs a non-negative numeric max`);
|
|
65
|
+
}
|
|
66
|
+
assertCompiles(d.pattern, at);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const a = rule.alter;
|
|
70
|
+
if (!a || typeof a !== 'object') throw configError(`${at}: missing alter block`);
|
|
71
|
+
if (!ALTER_KINDS.has(a.kind)) throw configError(`${at}: invalid alter.kind "${a.kind}"`);
|
|
72
|
+
if (typeof a.guidance !== 'string' || a.guidance.length === 0) {
|
|
73
|
+
throw configError(`${at}: alter needs guidance`);
|
|
74
|
+
}
|
|
75
|
+
if (a.kind === 'swap') {
|
|
76
|
+
if (!Array.isArray(a.with) || a.with.length === 0 || !a.with.every((w) => typeof w === 'string')) {
|
|
77
|
+
throw configError(`${at}: swap alter needs a non-empty array of string replacements`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (typeof rule.rationale !== 'string' || rule.rationale.length === 0) {
|
|
82
|
+
throw configError(`${at}: missing rationale`);
|
|
83
|
+
}
|
|
84
|
+
if (typeof rule.source !== 'string' || rule.source.length === 0) {
|
|
85
|
+
throw configError(`${at}: missing source`);
|
|
86
|
+
}
|
|
87
|
+
return id;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function assertCompiles(pattern, at) {
|
|
91
|
+
try {
|
|
92
|
+
new RegExp(pattern);
|
|
93
|
+
} catch (e) {
|
|
94
|
+
throw configError(`${at}: detect pattern does not compile: ${e.message}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Validate a parsed corpus (array of rules). Returns the rules unchanged.
|
|
99
|
+
export function validateCorpus(rules) {
|
|
100
|
+
if (!Array.isArray(rules)) throw configError('rule corpus must be a JSON array');
|
|
101
|
+
const seen = new Set();
|
|
102
|
+
rules.forEach((rule, i) => {
|
|
103
|
+
const id = validateRule(rule, `corpus[${i}]`);
|
|
104
|
+
if (seen.has(id)) throw configError(`duplicate ruleId "${id}"`);
|
|
105
|
+
seen.add(id);
|
|
106
|
+
});
|
|
107
|
+
return rules;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Read, parse, and validate the corpus at `path`.
|
|
111
|
+
export async function loadRules(path) {
|
|
112
|
+
let raw;
|
|
113
|
+
try {
|
|
114
|
+
raw = await readFile(path, 'utf8');
|
|
115
|
+
} catch (e) {
|
|
116
|
+
throw configError(`cannot read rules file ${path}: ${e.message}`);
|
|
117
|
+
}
|
|
118
|
+
let parsed;
|
|
119
|
+
try {
|
|
120
|
+
parsed = JSON.parse(raw);
|
|
121
|
+
} catch (e) {
|
|
122
|
+
throw configError(`rules file ${path} is not valid JSON: ${e.message}`);
|
|
123
|
+
}
|
|
124
|
+
return validateCorpus(parsed);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Rule applies if it targets the active surface ("all" matches every surface).
|
|
128
|
+
export function ruleAppliesToSurface(rule, surface) {
|
|
129
|
+
return rule.surface.includes('all') || rule.surface.includes(surface);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export { CATEGORIES, SEVERITIES, SURFACES, DETECT_TYPES, ALTER_KINDS };
|