elanous 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/06ef2eba-d1b99ac6d2ea663c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1413-7179a49332db5a80.js +1 -0
- package/apps/pwa/out/_next/static/chunks/150-25c277137a9638ab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1640-bc99ec23bbe29d12.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2415.ed6428ff334c7ce2.js → 1751.50b96ea9c37b49b6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2300-de059d0ab2a16630.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2684-05b0a81e75298c16.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3026-2ed7e317d9b02102.js +25 -0
- package/apps/pwa/out/_next/static/chunks/3611-77496ada38cd6358.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3717-68957c4dc9ceec90.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3929-46ba9af6f9d895cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4015-62c5a3744698a8da.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4217-3c4317a7905f9eeb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4240-3230f230fae2d7d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4271-e53e075ebd48593b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4377.3b125fef79576dfd.js +5 -0
- package/apps/pwa/out/_next/static/chunks/4440-18bb160d95819f4e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4853-35fdbe6bdbcdc0fc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2ea96958-e8c7e112de50b067.js → 4daa2798-1363d725c79f3593.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5007-6a3b30e23ff68a6e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5244-12b424c42070f5ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5791-bb82743fa6299562.js → 5348-bbf838e119661eee.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5391-8793f88b03b30161.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5611-df00af3e4e4a61bb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5890-dca2321773be829a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6123-37c9dc57ddaa7ab0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6588-b7975304a302ab91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6604-c03961962b6273b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7109-cf8c87e968e83a65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7178-30f77877525a2efb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7262-871563fc9663730b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7294-f0eeebb92aa93459.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7429-881af44deed6958f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7527.943753b6ed829f09.js +1 -0
- package/apps/pwa/out/_next/static/chunks/764-a01e858525a190c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7868-e35d5f7985ecd3f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8320-51484af7b01fdfa5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 8617.465ca22a319c95b3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8918-482b30ce2a8ebf4c.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/9131-52f5fad51cceca04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9238.2cd5f47f4bf7a2c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 9704-3221c102623c8d73.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-70c404c7bb6d7f0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-3ffd80166b1050aa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f89e9a303515a033.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-4f403cd060442c95.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-9fa879ebf5959096.js → page-0ac5d053f0444d21.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-4bcc63e272ae8205.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-880734bd07d62882.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-4472ddf1e2458e69.js → page-72bd384898d88955.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-1018bbc9586ecae5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-de86f8c38076dd7b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-70c404c7bb6d7f0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-cd6447196d8bafe6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-e733630c47fa102b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-d1c1a8357b161875.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-60c514128e81d787.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8b637b7e325e13c1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-112f7e477bda77fd.js → page-bccce0babceae30f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-63301d11dbe73836.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-a8cf0dc472060db2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-02fba54881ccfa49.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-d5e8e90f0038f8de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-b1ef6824f494466a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-d589bb0f233b1009.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c9bbaebef4cda059.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-3639ce7916b92fed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7450ae7478955653.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-70c404c7bb6d7f0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-b41ca4abfd618452.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-b333dd79ebee3fa9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-8be1227492793655.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-6e03e9371b552e0d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-911411e9fca18d03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-792170e649b37088.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c40463466937bc91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-509c798b80384536.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-1cac2aa21d77c417.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-9626799d9b4a2cd1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-84cd419494e16b0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → f6f3850d-7f324c6566002113.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/framework-361487d2da603917.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-607f5beb94cb2e07.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-d0222d219e29dddf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-a26980a31126decc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-edb2b37decf23e4d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-e5bea36f183b25e6.js → webpack-2bf59c720fe9542c.js} +1 -1
- package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +1 -0
- package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → ktMqfrBbf8BMGac0jqLrb}/_buildManifest.js +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +29 -27
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +29 -27
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +29 -27
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +26 -24
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +26 -24
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +26 -24
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +29 -27
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +29 -27
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +29 -27
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/icons/elanous-16.png +0 -0
- package/apps/pwa/out/icons/elanous-180.png +0 -0
- package/apps/pwa/out/icons/elanous-192.png +0 -0
- package/apps/pwa/out/icons/elanous-32.png +0 -0
- package/apps/pwa/out/icons/elanous-512.png +0 -0
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +26 -24
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +29 -27
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +29 -27
- package/apps/pwa/out/manifest.webmanifest +5 -2
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +26 -24
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +26 -24
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +29 -27
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +29 -27
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +27 -25
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +29 -27
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +26 -24
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +29 -27
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +29 -27
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +29 -27
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +29 -27
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +29 -27
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +26 -24
- package/apps/pwa/out/sw-strategies.js +44 -0
- package/apps/pwa/out/sw.js +6 -44
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +29 -27
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +29 -27
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +29 -27
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +29 -27
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +29 -27
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +29 -27
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +29 -27
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +29 -27
- package/catalog/resources.yaml +6 -6
- package/docs/goal-context/steward-directives-2026-09-28.md +35 -37
- package/graphs/landing/landing-heal.yaml +1 -1
- package/graphs/release/recipes.yaml +8 -1
- package/graphs/release/release-loop.yaml +6 -1
- package/graphs/video/README.md +1 -0
- package/graphs/video/explainer-line.yaml +58 -0
- package/package.json +3 -1
- package/plugins/job-coach/.codex-plugin/plugin.json +18 -1
- package/plugins/job-coach/README.md +12 -0
- package/plugins/job-coach/connectors/ncs/server.ts +4 -3
- package/plugins/job-coach/graphs/gap-judge.ts +44 -0
- package/plugins/job-coach/graphs/recipes.yaml +27 -0
- package/plugins/job-coach/graphs/report-enterprise.yaml +28 -0
- package/plugins/job-coach/graphs/run-step.ts +211 -17
- package/plugins/job-coach/plugin.json +16 -3
- package/plugins/job-coach/skills/enterprise-ai-fit/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-competency/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-needs/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-roadmap/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-task-analysis/SKILL.md +8 -0
- package/plugins/job-coach/skills/interview-to-profile/SKILL.md +18 -1
- package/scripts/backup/elanous-backup.sh +4 -1
- package/scripts/build-docs-site.ts +4 -3
- package/scripts/coord-channel-watch.sh +23 -4
- package/scripts/coord-post.sh +49 -12
- package/scripts/coord-tracks.json +5 -5
- package/scripts/eval-bot-vs-grok.ts +79 -0
- package/scripts/eval-grok-live-baseline.ts +36 -0
- package/scripts/fixtures/front-route-corpus.sample.json +17 -0
- package/scripts/fleet/node-setup.sh +46 -7
- package/scripts/landing-heal/must-fix-match.ts +26 -0
- package/scripts/landing-heal/run-mission.ts +114 -20
- package/scripts/lib/front-route-measurement.ts +131 -0
- package/scripts/lib/tui-sim-keys.ts +42 -0
- package/scripts/measure-front-route-classifier.ts +56 -0
- package/scripts/media/ocr-leak-scan.sh +21 -0
- package/scripts/public-export.ts +2 -3
- package/scripts/public-import-pr.ts +80 -6
- package/scripts/publish-internal-dist.ts +2 -3
- package/scripts/red-sweep-diff.ts +1 -1
- package/scripts/release-loop/auto-approve-node.ts +72 -0
- package/scripts/release-loop/gate-node.ts +332 -44
- package/scripts/release-loop/publish-node.ts +7 -3
- package/scripts/release-loop/release-docs.ts +20 -13
- package/scripts/release-loop/test-durations.ts +63 -0
- package/scripts/release-loop/upgrade-node.ts +3 -2
- package/scripts/test-audit-coverage-per-file.sh +16 -0
- package/scripts/test-audit-meaningless.ts +88 -0
- package/scripts/test-audit-overlap-and-weak.ts +73 -0
- package/scripts/test-deterministic.ts +27 -5
- package/scripts/tui-sim.ts +59 -14
- package/scripts/ux/bump-installed.sh +20 -0
- package/scripts/ux/lib/cdp.ts +72 -0
- package/scripts/ux/record-page.ts +50 -0
- package/skills/explainer-video/SKILL.md +99 -0
- package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -0
- package/skills/explainer-video/engine/build.mjs +431 -0
- package/skills/explainer-video/engine/vo.ts +37 -0
- package/skills/explainer-video/examples/elanous-0.2.5/script.json +155 -0
- package/src/agent/nested-agent-env.ts +1 -0
- package/src/agent-mission/driver.ts +95 -16
- package/src/agent-mission/mission-cli.ts +6 -0
- package/src/agent-mission/resource-ladder.ts +73 -25
- package/src/auth/acp-token.ts +16 -0
- package/src/auth/github-app-token.ts +112 -0
- package/src/autopilot/gh-rest-fallback.ts +177 -0
- package/src/autopilot/pr-manager.ts +51 -4
- package/src/boot/acp-server.ts +2 -16
- package/src/boot/daemon-tools/web-search.ts +2 -1
- package/src/boot/ws-bridge.ts +11 -0
- package/src/chat/index.ts +37 -8
- package/src/chat/log-scroll-key.ts +12 -0
- package/src/chat/submit-trace.ts +30 -0
- package/src/cli/agent-cli.ts +7 -2
- package/src/cli/decisions-cli.ts +94 -0
- package/src/cli/doctor-cli.ts +40 -54
- package/src/cli/doctor-readiness.ts +16 -24
- package/src/cli/intake-cli.ts +1 -1
- package/src/cli/mcp-call.ts +9 -0
- package/src/cli/mcp-gateway-cli.ts +7 -3
- package/src/cli/plugin-cli.ts +109 -11
- package/src/cli/pr-lineage.ts +2 -1
- package/src/cli/pwa-start.ts +3 -0
- package/src/cli/release-cli.ts +94 -7
- package/src/cli/self-update.ts +7 -4
- package/src/cli/start-cli.ts +33 -14
- package/src/dashboard/acp-boot.ts +3 -0
- package/src/dashboard/index.ts +62 -13
- package/src/dashboard/input/no-provider-reply.ts +13 -0
- package/src/dashboard/slash-runtime/unknown-slash-reply.ts +78 -0
- package/src/dashboard/stale-install-check.ts +77 -0
- package/src/decisions/decision-ledger.ts +404 -0
- package/src/domains/outbound-alert.ts +8 -2
- package/src/domains/telegram-kind-route.ts +21 -0
- package/src/execution-loop/dispatch-task.ts +139 -0
- package/src/execution-loop/gate-decision-summary.ts +29 -0
- package/src/execution-loop/gates.ts +197 -0
- package/src/graph-kinds/installed-plugin-nodes.ts +95 -0
- package/src/graph-kinds/plugin-nodes.ts +10 -2
- package/src/graph-runner/runner.ts +35 -5
- package/src/grounding/token.ts +12 -6
- package/src/harness/harness-cli-command.ts +68 -9
- package/src/harness/harness-pod-dispatch.ts +8 -2
- package/src/harness/harness-stop.ts +147 -18
- package/src/harness/self-send-decide.ts +26 -17
- package/src/harness/self-send-target.ts +22 -1
- package/src/harness/worktree-branch-prefix.ts +4 -3
- package/src/index.ts +56 -13
- package/src/instance/resolve.ts +6 -0
- package/src/intake-plane/collect-telegram-saved.ts +5 -0
- package/src/intake-plane/digest.ts +11 -2
- package/src/live/detail-switch.ts +8 -3
- package/src/llm/usable-llm.ts +32 -0
- package/src/loops/registry.ts +27 -5
- package/src/market/publish.ts +59 -3
- package/src/native-tool-catalog.ts +2 -2
- package/src/nexus/api/graph-approvals.ts +125 -0
- package/src/nexus/api/http-server.ts +60 -6
- package/src/nexus/api/log-fabric.ts +10 -1
- package/src/nexus/api/mcp-oauth-hint.ts +10 -9
- package/src/nexus/api/merge-approvals.ts +2 -1
- package/src/nexus/api/meta-api.ts +14 -0
- package/src/nexus/api/plugins-credentials-api.ts +47 -0
- package/src/nexus/api/plugins-market.ts +87 -3
- package/src/nexus/api/pod-credential-api.ts +68 -14
- package/src/nexus/api/public-routes.ts +7 -0
- package/src/nexus/api/trace.ts +57 -6
- package/src/nexus/boot/register-mcp-clients.ts +99 -14
- package/src/nexus/index.ts +33 -32
- package/src/nexus/outbound/router.ts +1 -1
- package/src/onboarding.ts +25 -2
- package/src/plugins/core/host.ts +14 -0
- package/src/plugins/install/market-fetch.ts +4 -3
- package/src/plugins/install/plugin-credentials.ts +106 -0
- package/src/plugins/install/plugin-install.ts +163 -32
- package/src/prompt-library/universal-preamble.ts +8 -2
- package/src/release-loop/checklist.ts +191 -0
- package/src/release-loop/release-note.ts +6 -3
- package/src/self-dev/dev-cli.ts +40 -20
- package/src/self-dev/dev-pipeline.ts +77 -1
- package/src/self-implement/gate-baseline.ts +3 -1
- package/src/self-implement/goal-author.ts +18 -1
- package/src/self-implement/host-regate.ts +6 -2
- package/src/self-implement/orchestrator.ts +7 -2
- package/src/self-implement/seams.ts +4 -2
- package/src/skills/tools/dev-harness.ts +42 -0
- package/src/steward/steward-cards.ts +45 -0
- package/src/steward/triage.ts +50 -14
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +61 -3
- package/src/task-orchestrator/surfaces/pod-command-job.ts +27 -6
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +39 -9
- package/src/task-orchestrator/surfaces/pod-source-bundle.ts +16 -1
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +51 -13
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +172 -28
- package/src/telegram-report.ts +12 -6
- package/src/tui-client/acp-transport-ws-client.ts +72 -5
- package/src/tui.ts +3 -2
- package/src/user-config.ts +28 -2
- package/src/util/spawn-sync-output.ts +15 -0
- package/src/version/code-revision.ts +1 -1
- package/src/version/packed-revision.json +1 -1
- package/src/web-search/free.ts +77 -0
- package/src/web-search/index.ts +2 -0
- package/src/workflow-runtime/deps-bridge.ts +4 -3
- package/src/workflow-runtime/plugin-kind-node.ts +32 -4
- package/src/workflow-runtime/types.ts +2 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1191-7b13a24117a689fa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2375-3fcae5186c64fe6b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +0 -25
- package/apps/pwa/out/_next/static/chunks/319-d37df1b6e0cbeff4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +0 -5
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4836-3c93795ab834381e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6090-1bd6077591603947.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7457-f5cb1cc8db9f7453.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8142-011e1f474efb8b33.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-0059d82bdb4812bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9740-52467f244755917f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +0 -1
- package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-21c3261af6ab377c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-1991445b8ea86274.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-4ebfaee2369fe248.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-ca7c3cc305c3285a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9951930add3d2051.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-4de08d4b766f134b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7a975e2b9db1dfb7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-49270714ef35bb96.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-33fbdf353e2a5982.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-259f122e2dd64317.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-65b82726753658e1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-4b8df3748dc06285.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-542a5c0006da637c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-63ffb347831262ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-98e4e359d97263b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-97e24ac675ce576e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-ac6391f26391d89d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9f45c9d45ffc43bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-36995b9cb5d98218.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-0c795b577988d63f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b4311e2f71c6735a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-eb2381074df20dba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-b01336e64eba01ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-7af7f21c1f2858ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-a98ecd364586c89b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-57f0502dfbfa089b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-3467d2e1abd8a14a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-7842ca23d53ba682.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-ae9ac882912b0198.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-3a0d72e3f783bcef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-f3b39fe8358c2a97.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-5a22af3181ac2afb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-1457627bb302a22c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
- package/apps/pwa/out/_next/static/css/e66b7f5efc8df3f3.css +0 -1
- /package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → ktMqfrBbf8BMGac0jqLrb}/_ssgManifest.js +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# Inputs gateRemote (SSH host, e.g. node-b) and gateRemoteMirror (bare mirror path, default ~/mirror/elanous-agent.git): run gate sweeps in temporary remote clones without a matching caller checkout; omit gateRemote for local execution.
|
|
1
2
|
graph_id: release-loop
|
|
2
3
|
loop:
|
|
3
4
|
title: 릴리스
|
|
@@ -17,6 +18,8 @@ nodes:
|
|
|
17
18
|
- { node_id: upgrade, kind: gate, recipe: 'cmd:upgrade', max_visits: 1 }
|
|
18
19
|
- { node_id: tui, kind: gate, recipe: 'cmd:tui', max_visits: 1 }
|
|
19
20
|
- { node_id: docs, kind: agent, recipe: 'cmd:docs', max_visits: 1 }
|
|
21
|
+
- { node_id: notes-check, kind: gate, recipe: 'cmd:notes-check', max_visits: 1 }
|
|
22
|
+
- { node_id: auto-approve, kind: gate, recipe: 'cmd:auto-approve', max_visits: 1 }
|
|
20
23
|
- { node_id: approve-publish, kind: hitl, recipe: 'approval:publish', max_visits: 1 }
|
|
21
24
|
- { node_id: publish, kind: git, recipe: 'cmd:publish-command', max_visits: 1 }
|
|
22
25
|
- { node_id: docs-land, kind: git, recipe: 'cmd:docs-land', max_visits: 1 }
|
|
@@ -32,7 +35,9 @@ edges:
|
|
|
32
35
|
- { from: prepare, on: outcome, map: { ok: upgrade, fail: failed, error: failed } }
|
|
33
36
|
- { from: upgrade, on: outcome, map: { ok: tui, fail: failed, error: failed } }
|
|
34
37
|
- { from: tui, on: outcome, map: { ok: docs, fail: failed, error: failed } }
|
|
35
|
-
- { from: docs, on: outcome, map: { ok:
|
|
38
|
+
- { from: docs, on: outcome, map: { ok: notes-check, fail: failed, error: failed } }
|
|
39
|
+
- { from: notes-check, on: outcome, map: { ok: auto-approve, fail: failed, error: failed } }
|
|
40
|
+
- { from: auto-approve, on: outcome, map: { ok: publish, fail: approve-publish, error: approve-publish } }
|
|
36
41
|
- { from: approve-publish, on: outcome, map: { ok: publish, fail: failed } }
|
|
37
42
|
- { from: publish, on: outcome, map: { ok: docs-land, fail: failed, error: failed } }
|
|
38
43
|
- { from: docs-land, on: outcome, map: { ok: verify, fail: failed, error: failed } }
|
package/graphs/video/README.md
CHANGED
|
@@ -42,6 +42,7 @@ fi
|
|
|
42
42
|
| `exec-standard.yaml` | *"이 파이프라인을 «정기적으로» 어떻게 도나"* | `delivered` · `skipped` · `failed` · `unobserved` |
|
|
43
43
|
| 🆕 `film-production-standard.yaml` | *"편집 앱을 스크립트로 몰아 한 편을 만들고 소셜까지 어떻게 내나"* | `delivered` · `master-only` · `host-blocked` · `blocked` · `unobserved` |
|
|
44
44
|
| 🆕 `character-video-standard.yaml` | *"레퍼런스에서 캐릭터를 세우고, 연출된 컷을 뽑고, 편집까지 어떻게 한 루프로 도나"* | `delivered` · `rendered-unedited` · `blocked` · `unobserved` |
|
|
45
|
+
| 🆕 `explainer-line.yaml` | *"사실 목록으로 «N가지 한 번에» 설명 영상을 어떻게 만들어 검사까지 가나"* (스킬 `explainer-video` · 팩 `video-explainer`) | `delivered` · `needs-human` · `blocked` · `unobserved` |
|
|
45
46
|
| `overlays/render-patient.yaml` | 「같은 회차에서 N번 되돌아왔다」면 예산을 올린다 | — |
|
|
46
47
|
| 🆕 `vlog-found-footage-pipeline.declaration.yaml` | *"남이 찍어 보낸 소재에서 «이야기»를 어떻게 뽑아 납품까지 가나"* | `delivered` · `needs-human` · `blocked` · `unobserved` |
|
|
47
48
|
| 🆕 `video-production-pipeline.declaration.yaml` | *"영상 한 편을 «어느 갈래든» 어떻게 만들어 납품까지 가나"* | `delivered` · `needs-human` · `blocked` · `unobserved` |
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# «N가지 한 번에 정리» explainer: facts → script → timed VO → composition → checked render → measured QC.
|
|
2
|
+
# Engine = skills/explainer-video (vo.ts · build.mjs). Render/snapshot/check = the existing hyperframes-render recipe.
|
|
3
|
+
# ⚠️ Recipes explainer-script / explainer-vo / explainer-build / explainer-qc are declared here and not yet coded
|
|
4
|
+
# (src/video-pipeline/recipes) — a walk stops at the first of them as «unknown recipe». Until then the skill runs these steps.
|
|
5
|
+
graph_id: explainer-line
|
|
6
|
+
version: 1
|
|
7
|
+
entry_node: script
|
|
8
|
+
terminal_nodes: [delivered, needs-human, blocked, unobserved]
|
|
9
|
+
nodes:
|
|
10
|
+
- node_id: script
|
|
11
|
+
kind: agent
|
|
12
|
+
recipe: explainer-script
|
|
13
|
+
max_visits: 3
|
|
14
|
+
contract: { inputs: [facts, sources], tools: read-only, outputs: [script_path] }
|
|
15
|
+
- node_id: vo
|
|
16
|
+
kind: agent
|
|
17
|
+
recipe: explainer-vo
|
|
18
|
+
max_visits: 2
|
|
19
|
+
contract: { inputs: [script_path], tools: network-write, outputs: [vo_dir, narration_seconds] }
|
|
20
|
+
- node_id: build
|
|
21
|
+
kind: agent
|
|
22
|
+
recipe: explainer-build
|
|
23
|
+
max_visits: 3
|
|
24
|
+
contract: { inputs: [script_path, vo_dir], tools: workspace-write, outputs: [hyperframes_project, timeline_path] }
|
|
25
|
+
- node_id: render
|
|
26
|
+
kind: gate
|
|
27
|
+
recipe: hyperframes-render
|
|
28
|
+
max_visits: 2
|
|
29
|
+
contract: { inputs: [hyperframes_project], tools: workspace-write, outputs: [hf_render_path, hf_snapshot_dir, hf_check] }
|
|
30
|
+
- node_id: qc
|
|
31
|
+
kind: gate
|
|
32
|
+
recipe: explainer-qc
|
|
33
|
+
max_visits: 2
|
|
34
|
+
contract: { inputs: [hf_render_path, script_path], tools: read-and-run, outputs: [vo_match, loudness_lufs, true_peak_db] }
|
|
35
|
+
- node_id: delivered
|
|
36
|
+
kind: judge
|
|
37
|
+
recipe: terminal-delivered
|
|
38
|
+
max_visits: 1
|
|
39
|
+
- node_id: needs-human
|
|
40
|
+
kind: judge
|
|
41
|
+
recipe: terminal-needs-human
|
|
42
|
+
max_visits: 1
|
|
43
|
+
- node_id: blocked
|
|
44
|
+
kind: judge
|
|
45
|
+
recipe: terminal-blocked
|
|
46
|
+
max_visits: 1
|
|
47
|
+
- node_id: unobserved
|
|
48
|
+
kind: judge
|
|
49
|
+
recipe: terminal-unobserved
|
|
50
|
+
max_visits: 1
|
|
51
|
+
edges:
|
|
52
|
+
- { from: script, on: outcome, map: { ok: vo, no-facts: needs-human, unmeasurable: unobserved } }
|
|
53
|
+
- { from: vo, on: outcome, map: { ok: build, no-key: needs-human, error: blocked, unmeasurable: unobserved } }
|
|
54
|
+
- { from: build, on: outcome, map: { ok: render, bad-script: script, error: blocked, unmeasurable: unobserved } }
|
|
55
|
+
# check/snapshot failures are layout faults → rebuild; a wrong-length render is a timing fault → rebuild too.
|
|
56
|
+
- { from: render, on: outcome, map: { ok: qc, check-fail: build, render-mismatch: build, unmeasurable: unobserved } }
|
|
57
|
+
# speech ≠ script → re-voice that line; loudness off → rebuild (mix lives in the composition); public release is a human call.
|
|
58
|
+
- { from: qc, on: outcome, map: { pass: delivered, vo-mismatch: vo, loudness: build, public: needs-human, unmeasurable: unobserved } }
|
package/package.json
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "elanous",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Self-healing coding harness where eyes, hands, and memory all turn on one sentence — it authors its own goals, implements them in isolated worktrees, and reviews its own work.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
+
"author": "Elanvital AI",
|
|
7
|
+
"homepage": "https://elanous.ai",
|
|
6
8
|
"repository": {
|
|
7
9
|
"type": "git",
|
|
8
10
|
"url": "git+https://github.com/ElanvitalAI/elanous.git"
|
|
@@ -2,5 +2,22 @@
|
|
|
2
2
|
"name": "job-coach",
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"description": "Interview-to-NCS competency gap and cited course report.",
|
|
5
|
-
"skills": "./skills/"
|
|
5
|
+
"skills": "./skills/",
|
|
6
|
+
"contributes": {
|
|
7
|
+
"skills": [
|
|
8
|
+
{ "name": "interview-to-profile" },
|
|
9
|
+
{ "name": "career-report" },
|
|
10
|
+
{ "name": "run-report" },
|
|
11
|
+
{ "name": "enterprise-needs" },
|
|
12
|
+
{ "name": "enterprise-task-analysis" },
|
|
13
|
+
{ "name": "enterprise-ai-fit" },
|
|
14
|
+
{ "name": "enterprise-competency" },
|
|
15
|
+
{ "name": "enterprise-roadmap" }
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"extensions": {
|
|
19
|
+
"ai.elanous": {
|
|
20
|
+
"graphs": ["./graphs/report.yaml", "./graphs/report-enterprise.yaml"]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
6
23
|
}
|
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
**S** 인터뷰에서 직무 후보를 추출해 공공데이터포털의 NCS 능력단위와 비교하고, 외부 코스 조사로 보고서를 작성한다. **C** 실제 NCS 키와 코스 출처가 없으면 매칭·추천을 지어낼 수 없다. **Q** 이 패키지를 받은 사용자가 어떻게 실행하고 결과를 확인하는가? **A** 아래 실행 방법과 검증 방법을 따른다.
|
|
4
4
|
|
|
5
|
+
## 개인 모드 · 기업 HRD 모드
|
|
6
|
+
|
|
7
|
+
개인 모드(`report.yaml`)는 `## 희망 직무`, `## 경험`, `## 보유 역량` 인터뷰를 읽고 직무 후보 → NCS 매칭 → 검증된 과정 조사 → 자기보고 역량 갭 → 개인 보고서로 진행한다. NCS 키가 없으면 실패하며 확인된 과정 URL이 없으면 추천을 `미확인`으로 표시한다. 개인 보고서의 다섯 섹션과 기존 `research` 재시도 계약은 유지한다.
|
|
8
|
+
|
|
9
|
+
기업 HRD 모드(`report-enterprise.yaml`)는 `## 기업 개요`, `## 교육 대상`, `## 현업 과제`, `## 교육 목표`, `## 현재 역량`, `## 교육 제약`의 인터뷰를 받는다. 가상 회사·가상 직원으로 입력을 시험하고 실제 직원 정보나 내부 정책을 예시 파일에 싣지 않는다. 같은 NCS 조회를 재사용해 니즈 → 현업 과제 → NCS → AI 적합성(비AI 대안 포함) → 역량 → 로드맵 → 교육 명세와 운영 경로를 만든 뒤 일곱 섹션을 판정한다. 섹션 또는 근거가 누락되면 보고 단계로 한 번 되돌리고 다시 판정한다. 인터뷰 진술은 자기보고이고, NCS 코드·과정/URL·사내 정책 및 AI 도입 승인은 조회·확인 전에는 `미확인`이다.
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
elanous graph run /absolute/path/to/installed/job-coach/graphs/report-enterprise.yaml --input '{"interview":"/private/path/to/fictional-enterprise.md"}' --json
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
기업 보고서는 `기업 개요 및 교육 대상`, `교육 니즈`, `직무·과제 및 NCS`, `AI 적합성`, `역량 진단`, `교육 로드맵`, `교육 명세·운영 경로` 순서다. NCS 조회에 실패하면 매칭을 발명하지 않고 런을 실패로 종료한다. `run-report` 스킬은 개인 보고서 진입이며 기업 모드는 다섯 개 기업 스킬(`enterprise-needs`, `enterprise-task-analysis`, `enterprise-ai-fit`, `enterprise-competency`, `enterprise-roadmap`)과 기업 그래프를 이용한다.
|
|
16
|
+
|
|
5
17
|
## 실행
|
|
6
18
|
|
|
7
19
|
공공데이터포털 [한국산업인력공단 NCS 기준정보 조회](https://www.data.go.kr/data/15128213/openapi.do) 활용신청 후 키를 **환경변수 `NCS_SERVICE_KEY`** 로 설정한다. 설치 시 `credentials` 이벤트의 `ncs.serviceKey` 에 입력하는 경로는 플러그인 설치기가 담당한다. 저장소에는 키를 넣지 않는다. `.mcp.json`은 MCP stdio 서버를 번들로 제공한다. 레시피는 러너가 제공하는 `ELANOUS_GRAPH_DIR`을 기준으로 번들 스크립트를 찾고, MCP의 `${CODEX_PLUGIN_ROOT}`는 Codex가 설치한 플러그인의 절대 경로로 치환한다. 런타임에는 `bun`과 PATH의 `elanous research --json`가 필요하다. 중첩 elanous 호출에는 `--test`를 붙여 격리한다. 인터뷰 파일은 `## 희망 직무`, `## 경험`, `## 보유 역량`을 포함해야 한다.
|
|
@@ -8,7 +8,7 @@ const schema = (properties: Record<string, unknown>, required: string[]) => ({ t
|
|
|
8
8
|
const text = { type: 'string' };
|
|
9
9
|
export const tools = [
|
|
10
10
|
{ name: 'ncs_search_units', description: 'Search NCS competency units by keyword (NCS007).', inputSchema: schema({ keyword: text }, ['keyword']) },
|
|
11
|
-
{ name: 'ncs_unit', description: 'Look up a competency unit by
|
|
11
|
+
{ name: 'ncs_unit', description: 'Look up a competency unit by its NCS_COMPE_UNIT_CD (NCS005).', inputSchema: schema({ code: text }, ['code']) },
|
|
12
12
|
{ name: 'ncs_classification', description: 'List NCS classifications at major, middle, minor or detailed level (NCS001–004). Parent codes are required below major.', inputSchema: schema({ level: { type: 'string', enum: ['major', 'middle', 'minor', 'detailed'] }, code: text }, ['level']) },
|
|
13
13
|
];
|
|
14
14
|
|
|
@@ -31,9 +31,10 @@ export async function callNcsTool(name: string, args: Arguments, options: { key?
|
|
|
31
31
|
} else if (name === 'ncs_unit') {
|
|
32
32
|
operation = 'NCS005';
|
|
33
33
|
const code = required(args.code, 'code');
|
|
34
|
-
if (!/^\d{8,}$/.test(code)) throw new Error('code must contain the complete numeric NCS
|
|
34
|
+
if (!/^\d{8,}$/.test(code)) throw new Error('code must contain the complete numeric NCS competency unit code');
|
|
35
35
|
for (const [field, start, end] of [['NCS_LCLAS_CD', 0, 2], ['NCS_MCLAS_CD', 2, 4], ['NCS_SCLAS_CD', 4, 6], ['NCS_SUBD_CD', 6, 8]] as const) params.set(field, code.slice(start, end));
|
|
36
|
-
params.set('NCS_CL_CD', code);
|
|
36
|
+
params.set('NCS_CL_CD', code.slice(0, 8));
|
|
37
|
+
params.set('NCS_COMPE_UNIT_CD', code);
|
|
37
38
|
} else if (name === 'ncs_classification') {
|
|
38
39
|
const levels = { major: 1, middle: 2, minor: 3, detailed: 4 } as const;
|
|
39
40
|
if (typeof args.level !== 'string' || !Object.hasOwn(levels, args.level)) throw new Error('level must be major, middle, minor or detailed');
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type GapUnit = { code: string; name: string; definition: string; level: string; elements: { name: string; criteria: string }[] };
|
|
2
|
+
export type GapVerdict = { code: string; name: string; status: '보유' | '부분' | '갭'; quote: string };
|
|
3
|
+
|
|
4
|
+
type Judge = (prompt: string) => Promise<string>;
|
|
5
|
+
export const MAX_GAP_PROMPT_CHARS = 12000;
|
|
6
|
+
|
|
7
|
+
export function gapPrompt(units: GapUnit[], interview: string): string {
|
|
8
|
+
return [
|
|
9
|
+
'인터뷰 원문과 공식 NCS 능력단위의 능력단위요소·수행준거·수준을 대조한다.',
|
|
10
|
+
'각 단위별 보유(수행준거를 충족하는 구체적 경험), 부분(일부만 입증), 갭(근거 없음) 중 하나를 판정한다.',
|
|
11
|
+
'단위 이름·보유 역량 목록만으로 보유를 추정하지 말고 원문에서 정확한 한 문장을 quote로 그대로 인용한다.',
|
|
12
|
+
'반드시 JSON 객체 {"gaps":[{"code":"...","status":"보유|부분|갭","quote":"..."}]}만 출력한다. 모든 code를 각각 한 번씩 포함한다.',
|
|
13
|
+
'NCS 단위:', JSON.stringify(units), '인터뷰 원문:', interview,
|
|
14
|
+
].join('\n');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function completeSentence(interview: string, quote: string): boolean {
|
|
18
|
+
if (!quote || /[\r\n]/.test(quote) || !/[.!?。!?]$/.test(quote) || /[.!?。!?]\s*\S/.test(quote.slice(0, -1))) return false;
|
|
19
|
+
let from = 0;
|
|
20
|
+
while (true) {
|
|
21
|
+
const index = interview.indexOf(quote, from);
|
|
22
|
+
if (index < 0) return false;
|
|
23
|
+
const before = interview.slice(0, index);
|
|
24
|
+
if (/(?:^|[.!?。!?]|\n)[ \t]*(?:[-*][ \t]+)?$/.test(before) && !/[\p{L}\p{N}]/u.test(interview[index + quote.length] ?? '')) return true;
|
|
25
|
+
from = index + 1;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function judgeGaps(units: GapUnit[], interview: string, judge: Judge): Promise<GapVerdict[]> {
|
|
30
|
+
const prompt = gapPrompt(units, interview);
|
|
31
|
+
if (prompt.length > MAX_GAP_PROMPT_CHARS) throw new Error('gap prompt exceeds input limit; interview or NCS detail is too long');
|
|
32
|
+
const answer = await judge(prompt);
|
|
33
|
+
let parsed: unknown;
|
|
34
|
+
try { parsed = JSON.parse(answer.replace(/^```(?:json)?\s*|\s*```$/g, '').trim()); } catch { parsed = {}; }
|
|
35
|
+
const rows = (parsed && typeof parsed === 'object' && 'gaps' in parsed && Array.isArray(parsed.gaps)) ? parsed.gaps : [];
|
|
36
|
+
return units.map(unit => {
|
|
37
|
+
const matches = rows.filter(row => row && typeof row === 'object' && row.code === unit.code);
|
|
38
|
+
const row = matches.length === 1 ? matches[0] as { status?: unknown; quote?: unknown } : {};
|
|
39
|
+
const quote = typeof row.quote === 'string' ? row.quote.trim() : '';
|
|
40
|
+
const isSentence = completeSentence(interview, quote);
|
|
41
|
+
const status = isSentence && (row.status === '보유' || row.status === '부분') ? row.status : '갭';
|
|
42
|
+
return { code: unit.code, name: unit.name, status, quote: isSentence ? quote : '' };
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -16,3 +16,30 @@ report:
|
|
|
16
16
|
judge:
|
|
17
17
|
command: 'bun "$ELANOUS_GRAPH_DIR/run-step.ts" judge'
|
|
18
18
|
timeout_ms: 30000
|
|
19
|
+
intake:
|
|
20
|
+
command: 'bun "$ELANOUS_GRAPH_DIR/run-step.ts" intake'
|
|
21
|
+
timeout_ms: 30000
|
|
22
|
+
needs:
|
|
23
|
+
command: 'bun "$ELANOUS_GRAPH_DIR/run-step.ts" needs'
|
|
24
|
+
timeout_ms: 30000
|
|
25
|
+
task:
|
|
26
|
+
command: 'bun "$ELANOUS_GRAPH_DIR/run-step.ts" task'
|
|
27
|
+
timeout_ms: 30000
|
|
28
|
+
ai-fit:
|
|
29
|
+
command: 'bun "$ELANOUS_GRAPH_DIR/run-step.ts" ai-fit'
|
|
30
|
+
timeout_ms: 30000
|
|
31
|
+
competency:
|
|
32
|
+
command: 'bun "$ELANOUS_GRAPH_DIR/run-step.ts" competency'
|
|
33
|
+
timeout_ms: 30000
|
|
34
|
+
roadmap:
|
|
35
|
+
command: 'bun "$ELANOUS_GRAPH_DIR/run-step.ts" roadmap'
|
|
36
|
+
timeout_ms: 30000
|
|
37
|
+
spec:
|
|
38
|
+
command: 'bun "$ELANOUS_GRAPH_DIR/run-step.ts" spec'
|
|
39
|
+
timeout_ms: 30000
|
|
40
|
+
routing:
|
|
41
|
+
command: 'bun "$ELANOUS_GRAPH_DIR/run-step.ts" routing'
|
|
42
|
+
timeout_ms: 30000
|
|
43
|
+
judge-enterprise:
|
|
44
|
+
command: 'bun "$ELANOUS_GRAPH_DIR/run-step.ts" judge-enterprise'
|
|
45
|
+
timeout_ms: 30000
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
graph_id: job-coach-report-enterprise
|
|
2
|
+
version: 1
|
|
3
|
+
entry_node: intake
|
|
4
|
+
terminal_nodes: [done, failed]
|
|
5
|
+
nodes:
|
|
6
|
+
- { node_id: intake, kind: agent, recipe: 'cmd:intake', max_visits: 1 }
|
|
7
|
+
- { node_id: needs, kind: agent, recipe: 'cmd:needs', max_visits: 1 }
|
|
8
|
+
- { node_id: task, kind: agent, recipe: 'cmd:task', max_visits: 1 }
|
|
9
|
+
- { node_id: ncs-match, kind: agent, recipe: 'cmd:ncs-match', max_visits: 1 }
|
|
10
|
+
- { node_id: ai-fit, kind: agent, recipe: 'cmd:ai-fit', max_visits: 2 }
|
|
11
|
+
- { node_id: competency, kind: agent, recipe: 'cmd:competency', max_visits: 2 }
|
|
12
|
+
- { node_id: roadmap, kind: agent, recipe: 'cmd:roadmap', max_visits: 2 }
|
|
13
|
+
- { node_id: spec, kind: agent, recipe: 'cmd:spec', max_visits: 2 }
|
|
14
|
+
- { node_id: routing, kind: agent, recipe: 'cmd:routing', max_visits: 2 }
|
|
15
|
+
- { node_id: judge, kind: judge, recipe: 'cmd:judge-enterprise', max_visits: 2 }
|
|
16
|
+
- { node_id: done, kind: gate, max_visits: 1 }
|
|
17
|
+
- { node_id: failed, kind: gate, max_visits: 1 }
|
|
18
|
+
edges:
|
|
19
|
+
- { from: intake, on: outcome, map: { ok: needs, fail: failed } }
|
|
20
|
+
- { from: needs, on: outcome, map: { ok: task, fail: failed } }
|
|
21
|
+
- { from: task, on: outcome, map: { ok: ncs-match, fail: failed } }
|
|
22
|
+
- { from: ncs-match, on: outcome, map: { ok: ai-fit, fail: failed } }
|
|
23
|
+
- { from: ai-fit, on: outcome, map: { ok: competency, fail: failed } }
|
|
24
|
+
- { from: competency, on: outcome, map: { ok: roadmap, fail: failed } }
|
|
25
|
+
- { from: roadmap, on: outcome, map: { ok: spec, fail: failed } }
|
|
26
|
+
- { from: spec, on: outcome, map: { ok: routing, fail: failed } }
|
|
27
|
+
- { from: routing, on: outcome, map: { ok: judge, fail: failed } }
|
|
28
|
+
- { from: judge, on: outcome, map: { ok: done, retry: ai-fit, fail: failed } }
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
3
|
import { dirname, join } from 'node:path';
|
|
3
4
|
import { callNcsTool } from '../connectors/ncs/server.js';
|
|
4
5
|
import { courseLinks } from './course-links.js';
|
|
6
|
+
import { gapPrompt, judgeGaps, MAX_GAP_PROMPT_CHARS, type GapUnit } from './gap-judge.js';
|
|
5
7
|
|
|
6
8
|
type Data = Record<string, unknown>;
|
|
7
9
|
const value = (object: unknown): Data => object && typeof object === 'object' && !Array.isArray(object) ? object as Data : {};
|
|
@@ -9,6 +11,30 @@ const text = (item: unknown) => typeof item === 'string' ? item.trim() : '';
|
|
|
9
11
|
const list = (item: unknown): string[] => Array.isArray(item) ? item.filter((v): v is string => typeof v === 'string' && !!v.trim()) : [];
|
|
10
12
|
const line = (item: unknown) => text(item).replace(/[\r\n|]/g, ' ');
|
|
11
13
|
const json = (data: Data) => console.log(JSON.stringify(data));
|
|
14
|
+
const items = (raw: unknown): Data[] => {
|
|
15
|
+
const item = value(raw).items ? value(value(raw).items).item : value(raw).item ?? raw;
|
|
16
|
+
return (Array.isArray(item) ? item : item ? [item] : []).map(value);
|
|
17
|
+
};
|
|
18
|
+
const pick = (record: Data, ...names: string[]) => names.map(name => line(record[name])).find(Boolean) ?? '';
|
|
19
|
+
const MAX_DETAIL_UNITS = 6;
|
|
20
|
+
const DETAIL_CONCURRENCY = 2;
|
|
21
|
+
const gapUnit = (unit: Data): GapUnit => ({
|
|
22
|
+
code: line(unit.code), name: line(unit.name), definition: line(unit.definition), level: line(unit.level),
|
|
23
|
+
elements: Array.isArray(unit.elements) ? unit.elements.map(value).map(element => ({ name: line(element.name), criteria: line(element.criteria) })) : [],
|
|
24
|
+
});
|
|
25
|
+
const detailFields = (raw: unknown) => {
|
|
26
|
+
const body = value(value(raw).body);
|
|
27
|
+
const records = items(body);
|
|
28
|
+
const detail = records[0] ?? body;
|
|
29
|
+
const elements = items(detail.COMPE_UNIT_ELEM ?? detail.competencyUnitElements ?? detail.elements ?? body.COMPE_UNIT_ELEM);
|
|
30
|
+
return {
|
|
31
|
+
level: pick(detail, 'COMPE_UNIT_LVL', 'COMPE_UNIT_LEVEL', 'NCS_LVL', 'level'),
|
|
32
|
+
elements: elements.map(element => ({
|
|
33
|
+
name: pick(element, 'COMPE_UNIT_ELEM_NAME', 'NCS_COMPE_UNIT_ELEM_NAME', 'name'),
|
|
34
|
+
criteria: pick(element, 'PERF_CRIT', 'PERF_CRITERIA', 'PERFORMANCE_CRITERIA', 'criteria'),
|
|
35
|
+
})).filter(element => element.name || element.criteria),
|
|
36
|
+
};
|
|
37
|
+
};
|
|
12
38
|
const context = value(JSON.parse(readFileSync(process.env.ELANOUS_GRAPH_CONTEXT ?? '', 'utf8')));
|
|
13
39
|
const input = value(context.input);
|
|
14
40
|
const outputs = value(context.outputs);
|
|
@@ -17,6 +43,11 @@ if (!contextsDir.endsWith('.json.contexts')) throw new Error('invalid graph run
|
|
|
17
43
|
const runStateFile = contextsDir.slice(0, -'.contexts'.length);
|
|
18
44
|
const reportDir = runStateFile.slice(0, -'.json'.length);
|
|
19
45
|
const reportFile = join(reportDir, 'report.md');
|
|
46
|
+
const enterpriseHeadings = ['기업 개요 및 교육 대상', '교육 니즈', '직무·과제 및 NCS', 'AI 적합성', '역량 진단', '교육 로드맵', '교육 명세·운영 경로'];
|
|
47
|
+
const interviewField = (interview: string, heading: string) => interview.split(`## ${heading}\n`)[1]?.split(/\n## /)[0]?.trim() ?? '';
|
|
48
|
+
const brief = (raw: string) => line(raw.replace(/^[-*]\s*/gm, '')).slice(0, 240) || '미확인';
|
|
49
|
+
const enterpriseOutput = (name: string) => value(outputs[name]);
|
|
50
|
+
const enterpriseLine = (label: string, raw: unknown) => `- ${label}: ${line(raw) || '미확인'}`;
|
|
20
51
|
|
|
21
52
|
async function run(step: string) {
|
|
22
53
|
if (step === 'profile') {
|
|
@@ -31,21 +62,170 @@ async function run(step: string) {
|
|
|
31
62
|
const skills = field('보유 역량').split(/[,\n]/).map(s => s.replace(/^[-*] /, '').trim()).filter(Boolean);
|
|
32
63
|
if (!jobs.length || !experiences.length || !skills.length) throw new Error('interview requires 희망 직무, 경험, 보유 역량 headings');
|
|
33
64
|
json({ jobs, skills, experienceCount: experiences.length });
|
|
65
|
+
} else if (step === 'intake') {
|
|
66
|
+
const path = text(input.interview);
|
|
67
|
+
if (!path || !path.endsWith('.md')) throw new Error('input.interview must name a Markdown interview file');
|
|
68
|
+
const interview = readFileSync(path, 'utf8').replace(/\r\n?/g, '\n');
|
|
69
|
+
const headings = ['기업 개요', '교육 대상', '현업 과제', '교육 목표', '현재 역량', '교육 제약'];
|
|
70
|
+
if (headings.some(heading => !interview.includes(`## ${heading}\n`))) throw new Error(`enterprise interview requires ${headings.join(', ')} headings`);
|
|
71
|
+
const task = interviewField(interview, '현업 과제').split(/\n|[,;]/).map(s => s.replace(/^[-*]\s*/, '').trim()).find(Boolean) ?? '';
|
|
72
|
+
if (!task || task === '미확인') throw new Error('missing enterprise task for NCS search');
|
|
73
|
+
for (const heading of ['교육 대상', '교육 목표']) {
|
|
74
|
+
const field = brief(interviewField(interview, heading));
|
|
75
|
+
if (field === '미확인') throw new Error(`missing enterprise ${heading}`);
|
|
76
|
+
}
|
|
77
|
+
const jobs = [line(task).slice(0, 80)];
|
|
78
|
+
json({ jobs, sections: Object.fromEntries(headings.map(heading => [heading, brief(interviewField(interview, heading))])) });
|
|
79
|
+
} else if (step === 'needs') {
|
|
80
|
+
const sections = value(enterpriseOutput('intake').sections);
|
|
81
|
+
if (!text(sections['현업 과제'])) throw new Error('missing enterprise intake');
|
|
82
|
+
json({ learnerGroup: sections['교육 대상'], task: sections['현업 과제'], requestedOutcome: sections['교육 목표'], evidence: '인터뷰 자기보고', baseline: '미확인' });
|
|
83
|
+
} else if (step === 'task') {
|
|
84
|
+
const needs = enterpriseOutput('needs');
|
|
85
|
+
if (!text(needs.task)) throw new Error('missing enterprise needs');
|
|
86
|
+
json({ work: needs.task, performanceCriterion: text(needs.requestedOutcome) || '미확인', source: '현업 과제·교육 목표 (인터뷰 자기보고)' });
|
|
34
87
|
} else if (step === 'ncs-match') {
|
|
35
|
-
const profile = value(outputs.profile);
|
|
88
|
+
const profile = value(outputs.profile ?? outputs.intake);
|
|
36
89
|
const jobs = list(profile.jobs);
|
|
37
90
|
if (!jobs.length) throw new Error('missing profile jobs');
|
|
38
91
|
const results = await Promise.all(jobs.slice(0, 3).map(async (job) => ({ job, result: await callNcsTool('ncs_search_units', { keyword: job }) })));
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
92
|
+
const named = results.flatMap(({ job, result }) => items(value(result).body).map(unit => ({
|
|
93
|
+
job, code: pick(unit, 'NCS_COMPE_UNIT_CD'), name: pick(unit, 'COMPE_UNIT_NAME'),
|
|
94
|
+
definition: pick(unit, 'COMPE_UNIT_DEF'), source: text(value(result).source),
|
|
95
|
+
})).filter(unit => unit.name));
|
|
96
|
+
// A hit without NCS_COMPE_UNIT_CD cannot be looked up (its classification code is not a unit code) — skip it, keep the rest.
|
|
97
|
+
const matches = named.filter(unit => unit.code);
|
|
98
|
+
if (named.length && !matches.length) throw new Error('NCS search returned named competency units only without NCS_COMPE_UNIT_CD; cannot invent a match');
|
|
99
|
+
if (!matches.length) throw new Error('NCS search returned no named competency units; cannot invent a match');
|
|
100
|
+
// Interleave jobs in search order; deduplicate codes before the bounded detail lookup.
|
|
101
|
+
const perJob = jobs.slice(0, 3).map(job => matches.filter(unit => unit.job === job));
|
|
102
|
+
const candidates: typeof matches = [];
|
|
103
|
+
const seen = new Set<string>();
|
|
104
|
+
for (let index = 0; candidates.length < MAX_DETAIL_UNITS && perJob.some(group => index < group.length); index++) {
|
|
105
|
+
for (const group of perJob) {
|
|
106
|
+
const unit = group[index];
|
|
107
|
+
if (unit && !seen.has(unit.code)) {
|
|
108
|
+
candidates.push(unit);
|
|
109
|
+
seen.add(unit.code);
|
|
110
|
+
if (candidates.length === MAX_DETAIL_UNITS) break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const details: (typeof matches[number] & ReturnType<typeof detailFields> & { source: string })[] = [];
|
|
115
|
+
for (let index = 0; index < candidates.length; index += DETAIL_CONCURRENCY) {
|
|
116
|
+
details.push(...await Promise.all(candidates.slice(index, index + DETAIL_CONCURRENCY).map(async unit => {
|
|
117
|
+
const detail = await callNcsTool('ncs_unit', { code: unit.code });
|
|
118
|
+
const fields = detailFields(detail);
|
|
119
|
+
if (!fields.level || !fields.elements.length || fields.elements.some(element => !element.name || !element.criteria)) {
|
|
120
|
+
throw new Error(`NCS unit ${unit.code} has no complete elements, performance criteria and level`);
|
|
121
|
+
}
|
|
122
|
+
return { ...unit, ...fields, source: text(value(detail).source) };
|
|
123
|
+
})));
|
|
124
|
+
}
|
|
125
|
+
const interview = readFileSync(text(input.interview), 'utf8');
|
|
126
|
+
const units: typeof details = [];
|
|
127
|
+
for (const detail of details) {
|
|
128
|
+
if (gapPrompt([...units, detail].map(unit => gapUnit(unit)), interview).length <= MAX_GAP_PROMPT_CHARS) units.push(detail);
|
|
129
|
+
}
|
|
130
|
+
if (!units.length) throw new Error('interview or NCS detail exceeds gap prompt input limit');
|
|
48
131
|
json({ units });
|
|
132
|
+
} else if (step === 'ai-fit') {
|
|
133
|
+
const task = enterpriseOutput('task');
|
|
134
|
+
const ncs = enterpriseOutput('ncs-match');
|
|
135
|
+
const units = Array.isArray(ncs.units) ? ncs.units.map(value) : [];
|
|
136
|
+
if (!text(task.work) || !units.length) throw new Error('missing task or NCS evidence for AI fit');
|
|
137
|
+
json({ task: task.work, ncsCodes: units.map(unit => text(unit.code)),
|
|
138
|
+
opportunity: '해당 과제의 AI 보조 가능성 검토 (효과 미확인)',
|
|
139
|
+
alternative: '업무 절차 정리 및 담당자 검토',
|
|
140
|
+
feasibility: '데이터 접근·보안·사내 정책 승인 미확인', decision: '조건부 검토' });
|
|
141
|
+
} else if (step === 'competency') {
|
|
142
|
+
const sections = value(enterpriseOutput('intake').sections);
|
|
143
|
+
const units = enterpriseOutput('ncs-match').units;
|
|
144
|
+
if (!Array.isArray(units) || !units.length) throw new Error('missing NCS units for competency assessment');
|
|
145
|
+
json({ current: sections['현재 역량'] || '미확인', status: '인터뷰 자기보고만 확인; 객관적 수준 미확인',
|
|
146
|
+
requirements: units.map(value).map(unit => ({ code: text(unit.code), name: text(unit.name), level: text(unit.level) || '미확인' })) });
|
|
147
|
+
} else if (step === 'roadmap') {
|
|
148
|
+
const competency = enterpriseOutput('competency');
|
|
149
|
+
const aiFit = enterpriseOutput('ai-fit');
|
|
150
|
+
if (!Array.isArray(competency.requirements) || !text(aiFit.decision)) throw new Error('missing competency or AI fit');
|
|
151
|
+
json({ phases: ['현재 수준 확인', '업무 기반 실습', '담당자 검토를 동반한 시범 적용', '업무 산출물 평가'],
|
|
152
|
+
assessment: '실제 업무 산출물의 수행 기준에 따른 관찰 평가', adoption: '제안 단계; 승인 미확인' });
|
|
153
|
+
} else if (step === 'spec') {
|
|
154
|
+
const roadmap = enterpriseOutput('roadmap');
|
|
155
|
+
const sections = value(enterpriseOutput('intake').sections);
|
|
156
|
+
if (!Array.isArray(roadmap.phases)) throw new Error('missing enterprise roadmap');
|
|
157
|
+
json({ audience: sections['교육 대상'] || '미확인', outcome: sections['교육 목표'] || '미확인',
|
|
158
|
+
activities: roadmap.phases, assessment: roadmap.assessment, constraints: sections['교육 제약'] || '미확인',
|
|
159
|
+
duration: '미확인', courses: '미확인' });
|
|
160
|
+
} else if (step === 'routing') {
|
|
161
|
+
const spec = enterpriseOutput('spec');
|
|
162
|
+
if (!Array.isArray(spec.activities)) throw new Error('missing enterprise training spec');
|
|
163
|
+
const sections = value(enterpriseOutput('intake').sections);
|
|
164
|
+
const ncs = enterpriseOutput('ncs-match');
|
|
165
|
+
const units = Array.isArray(ncs.units) ? ncs.units.map(value) : [];
|
|
166
|
+
if (!units.length) throw new Error('cannot report without verified NCS units');
|
|
167
|
+
const requirements = enterpriseOutput('competency').requirements;
|
|
168
|
+
if (!Array.isArray(requirements) || !requirements.length) throw new Error('missing competency requirements');
|
|
169
|
+
const md = ['# 기업 HRD 교육 제안 보고서', '',
|
|
170
|
+
`## ${enterpriseHeadings[0]}`, enterpriseLine('기업 개요 (인터뷰 자기보고)', sections['기업 개요']), enterpriseLine('교육 대상 (인터뷰 자기보고)', sections['교육 대상']), '',
|
|
171
|
+
`## ${enterpriseHeadings[1]}`, enterpriseLine('교육 대상', enterpriseOutput('needs').learnerGroup), enterpriseLine('현업 니즈 (인터뷰 자기보고)', enterpriseOutput('needs').task), enterpriseLine('교육 목표', enterpriseOutput('needs').requestedOutcome), enterpriseLine('근거', enterpriseOutput('needs').evidence), enterpriseLine('현재 수준', enterpriseOutput('needs').baseline), '',
|
|
172
|
+
`## ${enterpriseHeadings[2]}`, enterpriseLine('직무 과제', enterpriseOutput('task').work), enterpriseLine('관찰할 수행 기준', enterpriseOutput('task').performanceCriterion), enterpriseLine('과제 출처', enterpriseOutput('task').source),
|
|
173
|
+
...units.map(unit => `- NCS ${line(unit.code)} — ${line(unit.name)} · 수준 ${line(unit.level) || '미확인'} · 출처 ${line(unit.source) || '미확인'}`), '',
|
|
174
|
+
`## ${enterpriseHeadings[3]}`, enterpriseLine('적용 검토 과제', enterpriseOutput('ai-fit').task), enterpriseLine('관련 NCS 코드', list(enterpriseOutput('ai-fit').ncsCodes).join(', ')), enterpriseLine('적용 가능성', enterpriseOutput('ai-fit').opportunity), enterpriseLine('비AI 대안', enterpriseOutput('ai-fit').alternative), enterpriseLine('도입 판정', enterpriseOutput('ai-fit').decision), enterpriseLine('정책 및 데이터 접근', enterpriseOutput('ai-fit').feasibility), '',
|
|
175
|
+
`## ${enterpriseHeadings[4]}`, enterpriseLine('현재 역량 (자기보고)', enterpriseOutput('competency').current), enterpriseLine('검증 상태', enterpriseOutput('competency').status),
|
|
176
|
+
...requirements.map(raw => value(raw)).map(unit => `- 요구 역량: NCS ${line(unit.code)} — ${line(unit.name)} · 수준 ${line(unit.level) || '미확인'}`), '',
|
|
177
|
+
`## ${enterpriseHeadings[5]}`, ...list(enterpriseOutput('roadmap').phases).map((phase, i) => `- ${i + 1}. ${line(phase)}`), enterpriseLine('평가', enterpriseOutput('roadmap').assessment), enterpriseLine('채택 상태', enterpriseOutput('roadmap').adoption), '',
|
|
178
|
+
`## ${enterpriseHeadings[6]}`, enterpriseLine('교육 대상', spec.audience), enterpriseLine('교육 목표', spec.outcome), enterpriseLine('평가 명세', spec.assessment), enterpriseLine('시간·예산·접근 제약', spec.constraints),
|
|
179
|
+
enterpriseLine('기간', spec.duration), enterpriseLine('추천 과정·URL', spec.courses), '- 운영 경로: 담당자와 비AI/AI 시범 방식을 검토; 데이터 접근·정책 승인은 미확인', '',
|
|
180
|
+
'※ 인터뷰는 자기보고이며 NCS는 확인된 조회 결과만 인용합니다. 제안은 회사의 승인된 정책이나 확정 교육 과정이 아닙니다.', ''].join('\n');
|
|
181
|
+
mkdirSync(reportDir, { recursive: true });
|
|
182
|
+
writeFileSync(reportFile, md, { mode: 0o600 });
|
|
183
|
+
json({ report: reportFile });
|
|
184
|
+
} else if (step === 'judge-enterprise') {
|
|
185
|
+
const report = text(enterpriseOutput('routing').report);
|
|
186
|
+
if (report !== reportFile) throw new Error('missing enterprise report path');
|
|
187
|
+
const content = readFileSync(reportFile, 'utf8');
|
|
188
|
+
const needs = enterpriseOutput('needs');
|
|
189
|
+
const task = enterpriseOutput('task');
|
|
190
|
+
const aiFit = enterpriseOutput('ai-fit');
|
|
191
|
+
const competency = enterpriseOutput('competency');
|
|
192
|
+
const roadmap = enterpriseOutput('roadmap');
|
|
193
|
+
const spec = enterpriseOutput('spec');
|
|
194
|
+
const sections = value(enterpriseOutput('intake').sections);
|
|
195
|
+
const units = Array.isArray(enterpriseOutput('ncs-match').units) ? (enterpriseOutput('ncs-match').units as unknown[]).map(value) : [];
|
|
196
|
+
const phases = list(roadmap.phases);
|
|
197
|
+
const requirements = Array.isArray(competency.requirements) ? competency.requirements.map(value) : [];
|
|
198
|
+
const substantive = (raw: unknown) => !!text(raw) && text(raw) !== '미확인';
|
|
199
|
+
const required: string[][] = [
|
|
200
|
+
[enterpriseLine('기업 개요 (인터뷰 자기보고)', sections['기업 개요']), enterpriseLine('교육 대상 (인터뷰 자기보고)', sections['교육 대상'])],
|
|
201
|
+
[enterpriseLine('교육 대상', needs.learnerGroup), enterpriseLine('현업 니즈 (인터뷰 자기보고)', needs.task), enterpriseLine('교육 목표', needs.requestedOutcome), enterpriseLine('근거', needs.evidence), enterpriseLine('현재 수준', needs.baseline)],
|
|
202
|
+
[enterpriseLine('직무 과제', task.work), enterpriseLine('관찰할 수행 기준', task.performanceCriterion), enterpriseLine('과제 출처', task.source),
|
|
203
|
+
...units.map(unit => `- NCS ${line(unit.code)} — ${line(unit.name)} · 수준 ${line(unit.level) || '미확인'} · 출처 ${line(unit.source) || '미확인'}`)],
|
|
204
|
+
[enterpriseLine('적용 검토 과제', aiFit.task), enterpriseLine('관련 NCS 코드', list(aiFit.ncsCodes).join(', ')), enterpriseLine('적용 가능성', aiFit.opportunity), enterpriseLine('비AI 대안', aiFit.alternative), enterpriseLine('도입 판정', aiFit.decision), enterpriseLine('정책 및 데이터 접근', aiFit.feasibility)],
|
|
205
|
+
[enterpriseLine('현재 역량 (자기보고)', competency.current), enterpriseLine('검증 상태', competency.status),
|
|
206
|
+
...requirements.map(unit => `- 요구 역량: NCS ${line(unit.code)} — ${line(unit.name)} · 수준 ${line(unit.level) || '미확인'}`)],
|
|
207
|
+
[...phases.map((phase, i) => `- ${i + 1}. ${line(phase)}`), enterpriseLine('평가', roadmap.assessment), enterpriseLine('채택 상태', roadmap.adoption)],
|
|
208
|
+
[enterpriseLine('교육 대상', spec.audience), enterpriseLine('교육 목표', spec.outcome), enterpriseLine('평가 명세', spec.assessment), enterpriseLine('시간·예산·접근 제약', spec.constraints), enterpriseLine('기간', spec.duration), enterpriseLine('추천 과정·URL', spec.courses), '- 운영 경로: 담당자와 비AI/AI 시범 방식을 검토; 데이터 접근·정책 승인은 미확인'],
|
|
209
|
+
];
|
|
210
|
+
const complete = enterpriseHeadings.every((heading, index) => {
|
|
211
|
+
const marker = `## ${heading}\n`;
|
|
212
|
+
const start = content.indexOf(marker);
|
|
213
|
+
const end = index < enterpriseHeadings.length - 1 ? content.indexOf(`## ${enterpriseHeadings[index + 1]}\n`, start + marker.length) : content.length;
|
|
214
|
+
if (start < 0 || end <= start || content.indexOf(marker, start + marker.length) !== -1) return false;
|
|
215
|
+
const lines = new Set(content.slice(start + marker.length, end).split('\n'));
|
|
216
|
+
return required[index]!.every(expected => lines.has(expected));
|
|
217
|
+
});
|
|
218
|
+
const hasNcs = units.length > 0 && units.every(unit => substantive(unit.code) && substantive(unit.name)) &&
|
|
219
|
+
list(aiFit.ncsCodes).join(', ') === units.map(unit => text(unit.code)).join(', ');
|
|
220
|
+
const hasRequiredInput = [sections['교육 대상'], sections['교육 목표'], needs.learnerGroup, needs.requestedOutcome, spec.audience, spec.outcome].every(substantive);
|
|
221
|
+
const hasRequirements = requirements.length === units.length && requirements.every((unit, index) =>
|
|
222
|
+
substantive(unit.code) && substantive(unit.name) && substantive(unit.level) &&
|
|
223
|
+
['code', 'name', 'level'].every(field => text(unit[field]) === text(units[index]?.[field])));
|
|
224
|
+
const hasDecision = [aiFit.task, aiFit.opportunity, aiFit.alternative, aiFit.decision, roadmap.assessment].every(substantive) &&
|
|
225
|
+
text(aiFit.task) === text(task.work) && phases.length >= 4 && phases.every(substantive);
|
|
226
|
+
const path = (JSON.parse(readFileSync(runStateFile, 'utf8')) as { path?: string[] }).path ?? [];
|
|
227
|
+
const visits = path.filter(node => node === 'judge' || node === 'judge-enterprise').length;
|
|
228
|
+
json({ outcome: complete && hasRequiredInput && hasNcs && hasRequirements && hasDecision ? 'ok' : visits < 2 ? 'retry' : 'fail', report });
|
|
49
229
|
} else if (step === 'research') {
|
|
50
230
|
const profile = value(outputs.profile);
|
|
51
231
|
const query = `${list(profile.jobs).join(' ')} 역량 교육 과정 강의 공식 출처`;
|
|
@@ -61,13 +241,27 @@ async function run(step: string) {
|
|
|
61
241
|
const courses = await courseLinks(result, units);
|
|
62
242
|
json({ courses });
|
|
63
243
|
} else if (step === 'gap') {
|
|
64
|
-
const profile = value(outputs.profile);
|
|
65
244
|
const ncs = value(outputs['ncs-match']);
|
|
66
|
-
const
|
|
67
|
-
const units = Array.isArray(ncs.units) ? ncs.units.map(value) : [];
|
|
245
|
+
const units = Array.isArray(ncs.units) ? ncs.units.map(value).map(gapUnit) : [];
|
|
68
246
|
if (!units.length) throw new Error('missing NCS units');
|
|
69
|
-
const
|
|
70
|
-
|
|
247
|
+
const interview = readFileSync(text(input.interview), 'utf8');
|
|
248
|
+
// A research retry revisits gap with the same units and interview — reuse only a verdict made from exactly those.
|
|
249
|
+
const basis = createHash('sha256').update(gapPrompt(units, interview)).digest('hex');
|
|
250
|
+
const previous = value(outputs.gap);
|
|
251
|
+
if (previous.basis === basis && Array.isArray(previous.gaps)) {
|
|
252
|
+
json({ gaps: previous.gaps, basis });
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const gaps = await judgeGaps(units, interview, async prompt => {
|
|
256
|
+
const cli = Bun.spawn(['elanous', '--test', 'ask', '--json', prompt], { env: process.env, stdout: 'pipe', stderr: 'pipe' });
|
|
257
|
+
const stdout = await new Response(cli.stdout).text();
|
|
258
|
+
await new Response(cli.stderr).text();
|
|
259
|
+
if (await cli.exited !== 0) throw new Error('gap LLM failed');
|
|
260
|
+
const response = value(JSON.parse(stdout));
|
|
261
|
+
if (typeof response.reply !== 'string') throw new Error('gap LLM returned no reply');
|
|
262
|
+
return response.reply;
|
|
263
|
+
});
|
|
264
|
+
json({ gaps, basis });
|
|
71
265
|
} else if (step === 'report') {
|
|
72
266
|
const profile = value(outputs.profile);
|
|
73
267
|
const ncs = value(outputs['ncs-match']);
|
|
@@ -78,8 +272,8 @@ async function run(step: string) {
|
|
|
78
272
|
const gaps = Array.isArray(gap.gaps) ? gap.gaps.map(value) : [];
|
|
79
273
|
if (!units.length || !gaps.length) throw new Error('cannot report without NCS units and gaps');
|
|
80
274
|
const md = ['# AI 직무코치 보고서', '', '## 직무', ...list(profile.jobs).map(x => `- ${line(x)}`), '',
|
|
81
|
-
'## NCS 능력단위 매칭', ...units.map(x => `- ${line(x.code)} — ${line(x.name)} (${line(x.job)})`), '',
|
|
82
|
-
'## 역량 갭', ...gaps.map(x => `- ${line(x.code)} ${line(x.name)}: ${line(x.status)}`), '',
|
|
275
|
+
'## NCS 능력단위 매칭', ...units.map(x => `- ${line(x.code)} — ${line(x.name)} (${line(x.job)}) · 수준 ${line(x.level) || '미확인'}${Array.isArray(x.elements) ? x.elements.map(value).map(element => ` · ${line(element.name)}: ${line(element.criteria)}`).join('') : ''}`), '',
|
|
276
|
+
'## 역량 갭', ...gaps.map(x => `- ${line(x.code)} ${line(x.name)}: ${line(x.status)}${text(x.quote) ? ` — 인터뷰 인용: “${line(x.quote)}”` : ''}`), '',
|
|
83
277
|
'## 추천 코스', ...(courses.length ? courses.map(x => `- [${line(x.title).replace(/[\[\]]/g, '')}](${text(x.url)}) — ${line(x.matchedUnit)}; ${line(x.evidence)}`) : ['- 확인된 관련 교육 과정 없음 (추천 미확인)']), '',
|
|
84
278
|
'## 출처', '- [한국산업인력공단 NCS 기준정보 조회](https://www.data.go.kr/data/15128213/openapi.do)',
|
|
85
279
|
...courses.map(x => `- ${text(x.url)}`), '', '※ 보유 역량은 인터뷰 자기보고이며, 확인되지 않은 역량은 갭으로 표시합니다.', ''].join('\n');
|
|
@@ -3,11 +3,24 @@
|
|
|
3
3
|
"name": "job-coach",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"description": "Interview-to-NCS competency gap and cited course report.",
|
|
6
|
+
"skills": "./skills/",
|
|
7
|
+
"contributes": {
|
|
8
|
+
"skills": [
|
|
9
|
+
{ "name": "interview-to-profile" },
|
|
10
|
+
{ "name": "career-report" },
|
|
11
|
+
{ "name": "run-report" },
|
|
12
|
+
{ "name": "enterprise-needs" },
|
|
13
|
+
{ "name": "enterprise-task-analysis" },
|
|
14
|
+
{ "name": "enterprise-ai-fit" },
|
|
15
|
+
{ "name": "enterprise-competency" },
|
|
16
|
+
{ "name": "enterprise-roadmap" }
|
|
17
|
+
]
|
|
18
|
+
},
|
|
6
19
|
"extensions": {
|
|
7
20
|
"ai.elanous": {
|
|
8
|
-
"graphs": ["./graphs/report.yaml"],
|
|
9
|
-
"capabilities": ["
|
|
10
|
-
"connectors": [{ "id": "ncs", "fields": [{ "name": "serviceKey", "secret": true }] }]
|
|
21
|
+
"graphs": ["./graphs/report.yaml", "./graphs/report-enterprise.yaml"],
|
|
22
|
+
"capabilities": ["fs:workdir", "net:apis.data.go.kr", "proc:bun", "proc:elanous", "secret:ncs"],
|
|
23
|
+
"connectors": [{ "id": "ncs", "fields": [{ "name": "serviceKey", "secret": true, "env": "NCS_SERVICE_KEY" }] }]
|
|
11
24
|
}
|
|
12
25
|
}
|
|
13
26
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: enterprise-ai-fit
|
|
3
|
+
description: Assess AI applicability to verified enterprise tasks, including non-AI alternatives and policy unknowns.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use at the enterprise `ai-fit` stage after `task` and the existing `ncs-match` stage. For each concrete task, compare a plausible AI-assisted workflow with a non-AI alternative. Record the input data type, expected benefit, failure mode, human review point, testable outcome and evidence from the task or confirmed NCS unit. Separate suitability from a promise of productivity gain. If data rights, access, privacy, security or organization approval are not documented, mark policy and deployment feasibility `미확인`; do not assert permission to upload internal information to an external model. When AI adds no justified value or a task is insufficiently described, recommend the non-AI or evidence-gathering route rather than an AI course.
|
|
7
|
+
|
|
8
|
+
Use only fictional company examples. Summarize the interview instead of copying quotations; any indispensable quotation must be brief, relevant and de-identified. Never fabricate an NCS code, course name, URL or internal policy; mark unknown details `미확인`. Hand the supported AI-fit and unresolved constraints to `competency`.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: enterprise-competency
|
|
3
|
+
description: Compare task and verified NCS requirements to self-reported enterprise competencies without inferring incapacity.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use after `enterprise-ai-fit` at the `competency` stage. Map each verified NCS unit's official code, name, level and criteria to the relevant task, keeping its source. Compare those requirements with `## 현재 역량` as self-report only. Classify each requirement as evidenced by interview, gap in evidence, or `미확인` where baseline evidence is absent; never equate missing evidence with inability or claim that an interview verifies mastery. For AI-suitable tasks, include human review, data handling and evaluation skills only insofar as the task and evidence require them. Link each proposed learning outcome to a task and an observable assessment; flag unverified NCS detail `미확인`, not an invented official unit.
|
|
7
|
+
|
|
8
|
+
Use fictional people and employers in examples. Do not persist interview quotations or confidential descriptions; at most use a short, de-identified excerpt when necessary for a specific finding. Course availability and URL, approval to use company data and applicable company policy remain `미확인` until checked. Send the evidence-backed competencies and uncertainties to `roadmap`.
|