@indigoai-us/hq-cli 5.77.13 → 5.78.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/CHANGELOG.md +34 -0
- package/assets/scaffold/core/scripts/archive-old-threads.sh +86 -0
- package/assets/scaffold/core/scripts/backfill-company-skill-mirrors.sh +73 -0
- package/assets/scaffold/core/scripts/backfill-workspace-mirror.sh +51 -0
- package/assets/scaffold/core/scripts/codex-skill-bridge.sh +550 -0
- package/assets/scaffold/core/scripts/detect-stale-core-policy-mirror.sh +185 -0
- package/assets/scaffold/core/scripts/hq-status-summary.sh +187 -0
- package/assets/scaffold/core/scripts/ontology-readme-drift.sh +264 -0
- package/assets/scaffold/core/scripts/qmd-reindex-after-sync.sh +93 -0
- package/assets/scaffold/core/scripts/rebuild-all-indexes.sh +69 -0
- package/assets/scaffold/core/scripts/rebuild-companies-index.sh +206 -0
- package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +127 -0
- package/assets/scaffold/core/scripts/rebuild-orchestrator-index.sh +86 -0
- package/assets/scaffold/core/scripts/rebuild-projects-index.sh +105 -0
- package/assets/scaffold/core/scripts/rebuild-public-knowledge-index.sh +83 -0
- package/assets/scaffold/core/scripts/rebuild-reports-index.sh +114 -0
- package/assets/scaffold/core/scripts/rebuild-social-drafts-index.sh +135 -0
- package/assets/scaffold/core/scripts/rebuild-threads-index.sh +82 -0
- package/assets/scaffold/core/scripts/rebuild-workers-index.sh +97 -0
- package/assets/scaffold/core/scripts/resize-screenshot.sh +47 -0
- package/assets/scaffold/core/scripts/token-usage-report.sh +254 -0
- package/assets/scaffold/core/scripts/worktree.sh +136 -0
- package/dist/commands/agents.js +28 -12
- package/dist/commands/api-keys.js +100 -20
- package/dist/commands/core.d.ts +90 -0
- package/dist/commands/core.js +279 -0
- package/dist/commands/files-browse.d.ts +21 -0
- package/dist/commands/files-browse.js +145 -24
- package/dist/commands/files.js +33 -7
- package/dist/commands/members.js +16 -5
- package/dist/commands/pack-install.d.ts +14 -0
- package/dist/commands/pack-install.js +51 -2
- package/dist/commands/pkg-install.js +43 -0
- package/dist/commands/run.js +8 -0
- package/dist/commands/secrets.js +125 -65
- package/dist/main.js +6 -0
- package/dist/utils/api-key-command-gate.d.ts +45 -0
- package/dist/utils/api-key-command-gate.js +51 -0
- package/dist/utils/hq-roots.d.ts +91 -0
- package/dist/utils/hq-roots.js +212 -0
- package/dist/utils/resolve-vault-credential.d.ts +63 -3
- package/dist/utils/resolve-vault-credential.js +152 -6
- package/dist/utils/run-bundled-script.d.ts +66 -0
- package/dist/utils/run-bundled-script.js +80 -0
- package/dist/utils/vault-api.d.ts +21 -0
- package/dist/utils/vault-api.js +74 -3
- package/package.json +7 -1
- package/.claude/policies/hq-cli-interactive-reads-use-sts-vend.md +0 -49
- package/.github/workflows/ci.yml +0 -46
- package/.github/workflows/publish.yml +0 -36
- package/.github/workflows/scripts/smoke-test-pkg.sh +0 -97
- package/e2e/cli.test.ts +0 -128
- package/eslint.config.js +0 -37
- package/pnpm-workspace.yaml +0 -4
- package/scripts/chmod-bins.mjs +0 -49
- package/scripts/generate-dsn.mjs +0 -26
- package/scripts/generate-scan-packages-table.mjs +0 -113
- package/scripts/smoke-sources-signals.sh +0 -103
- package/src/bin/hq-auth-refresh.ts +0 -43
- package/src/cli-version.ts +0 -13
- package/src/commands/__fixtures__/make-tar.ts +0 -126
- package/src/commands/_patterns.ts +0 -10
- package/src/commands/add.ts +0 -75
- package/src/commands/agents.test.ts +0 -527
- package/src/commands/agents.ts +0 -923
- package/src/commands/api-keys.test.ts +0 -291
- package/src/commands/api-keys.ts +0 -382
- package/src/commands/artifact-verify.test.ts +0 -177
- package/src/commands/auth.test.ts +0 -82
- package/src/commands/auth.ts +0 -213
- package/src/commands/billing.test.ts +0 -158
- package/src/commands/billing.ts +0 -146
- package/src/commands/channels.test.ts +0 -74
- package/src/commands/channels.ts +0 -91
- package/src/commands/cloud-demote.test.ts +0 -401
- package/src/commands/cloud-demote.ts +0 -278
- package/src/commands/cloud-provision.test.ts +0 -1117
- package/src/commands/cloud-provision.ts +0 -942
- package/src/commands/cloud.pull-all.test.ts +0 -711
- package/src/commands/cloud.pull-per-company.test.ts +0 -188
- package/src/commands/cloud.push-all.test.ts +0 -485
- package/src/commands/cloud.scope-excluded-warning.test.ts +0 -22
- package/src/commands/cloud.selectors.test.ts +0 -208
- package/src/commands/cloud.status.test.ts +0 -160
- package/src/commands/cloud.test.ts +0 -41
- package/src/commands/cloud.ts +0 -2130
- package/src/commands/company.test.ts +0 -202
- package/src/commands/company.ts +0 -148
- package/src/commands/creators.test.ts +0 -60
- package/src/commands/creators.ts +0 -117
- package/src/commands/crm.test.ts +0 -179
- package/src/commands/crm.ts +0 -236
- package/src/commands/db-migrate.ts +0 -55
- package/src/commands/db-provision.ts +0 -114
- package/src/commands/db-sql.ts +0 -124
- package/src/commands/db-status.ts +0 -108
- package/src/commands/db.ts +0 -26
- package/src/commands/dm.test.ts +0 -748
- package/src/commands/dm.ts +0 -1226
- package/src/commands/feedback.test.ts +0 -413
- package/src/commands/feedback.ts +0 -169
- package/src/commands/files-browse.test.ts +0 -1249
- package/src/commands/files-browse.ts +0 -1506
- package/src/commands/files-delete.test.ts +0 -484
- package/src/commands/files-recovery.test.ts +0 -370
- package/src/commands/files.test.ts +0 -722
- package/src/commands/files.ts +0 -1446
- package/src/commands/group-grants.test.ts +0 -330
- package/src/commands/group-grants.ts +0 -455
- package/src/commands/groups.test.ts +0 -44
- package/src/commands/groups.ts +0 -368
- package/src/commands/integrations.test.ts +0 -633
- package/src/commands/integrations.ts +0 -507
- package/src/commands/list.ts +0 -85
- package/src/commands/login.ts +0 -52
- package/src/commands/logout.ts +0 -30
- package/src/commands/marketplace-install.test.ts +0 -543
- package/src/commands/marketplace-security.test.ts +0 -646
- package/src/commands/mcp-registration.test.ts +0 -2787
- package/src/commands/mcp-registration.ts +0 -2612
- package/src/commands/mcp-status.test.ts +0 -483
- package/src/commands/mcp-status.ts +0 -575
- package/src/commands/mcp-status.us011.test.ts +0 -243
- package/src/commands/meetings.test.ts +0 -522
- package/src/commands/meetings.ts +0 -789
- package/src/commands/members.test.ts +0 -1474
- package/src/commands/members.ts +0 -1181
- package/src/commands/onboard-identity-guard.test.ts +0 -99
- package/src/commands/onboard-identity-guard.ts +0 -103
- package/src/commands/onboard-join.test.ts +0 -96
- package/src/commands/onboard-join.ts +0 -101
- package/src/commands/onboard-warning.test.ts +0 -26
- package/src/commands/onboard-warning.ts +0 -12
- package/src/commands/onboard.ts +0 -267
- package/src/commands/outposts-heartbeat.test.ts +0 -299
- package/src/commands/outposts-heartbeat.ts +0 -310
- package/src/commands/outposts-replica-sync.test.ts +0 -272
- package/src/commands/outposts-self-deploy.test.ts +0 -243
- package/src/commands/outposts.test.ts +0 -1039
- package/src/commands/outposts.ts +0 -1935
- package/src/commands/pack-install-secret-authorization.test.ts +0 -115
- package/src/commands/pack-install.test.ts +0 -1469
- package/src/commands/pack-install.ts +0 -2247
- package/src/commands/pack-update-cache.test.ts +0 -149
- package/src/commands/packs.test.ts +0 -88
- package/src/commands/packs.ts +0 -622
- package/src/commands/people.test.ts +0 -807
- package/src/commands/people.ts +0 -422
- package/src/commands/pkg-install.dispatch.test.ts +0 -71
- package/src/commands/pkg-install.ts +0 -215
- package/src/commands/pkg-list.test.ts +0 -79
- package/src/commands/pkg-list.ts +0 -13
- package/src/commands/pkg-remove.ts +0 -71
- package/src/commands/pkg-update.ts +0 -189
- package/src/commands/publish.test.ts +0 -650
- package/src/commands/publish.ts +0 -580
- package/src/commands/reindex.test.ts +0 -259
- package/src/commands/reindex.ts +0 -274
- package/src/commands/rescue.test.ts +0 -39
- package/src/commands/rescue.ts +0 -210
- package/src/commands/run.env-local.test.ts +0 -88
- package/src/commands/run.test.ts +0 -45
- package/src/commands/run.ts +0 -184
- package/src/commands/safe-extract.test.ts +0 -459
- package/src/commands/safe-extract.ts +0 -444
- package/src/commands/secrets-scope.test.ts +0 -56
- package/src/commands/secrets-scope.ts +0 -32
- package/src/commands/secrets.parse-destination.test.ts +0 -38
- package/src/commands/secrets.test.ts +0 -2037
- package/src/commands/secrets.ts +0 -2240
- package/src/commands/signals.ts +0 -351
- package/src/commands/skill.test.ts +0 -770
- package/src/commands/skill.ts +0 -796
- package/src/commands/sources.ts +0 -362
- package/src/commands/sync-mode.test.ts +0 -366
- package/src/commands/sync-mode.ts +0 -387
- package/src/commands/sync-narrow.test.ts +0 -633
- package/src/commands/sync-narrow.ts +0 -555
- package/src/commands/sync.ts +0 -165
- package/src/commands/team-sync.ts +0 -628
- package/src/commands/update.ts +0 -71
- package/src/commands/whoami.ts +0 -83
- package/src/commands/workers.test.ts +0 -158
- package/src/commands/workers.ts +0 -301
- package/src/index.test.ts +0 -32
- package/src/index.ts +0 -20
- package/src/lib/db/control-plane.test.ts +0 -59
- package/src/lib/db/control-plane.ts +0 -113
- package/src/lib/db/local.test.ts +0 -81
- package/src/lib/db/local.ts +0 -148
- package/src/lib/db/migrate.test.ts +0 -133
- package/src/lib/db/migrate.ts +0 -137
- package/src/lib/db/paths.test.ts +0 -112
- package/src/lib/db/paths.ts +0 -128
- package/src/lib/db/remote-engine.test.ts +0 -44
- package/src/lib/db/remote-engine.ts +0 -148
- package/src/lib/db/remote-sql.test.ts +0 -32
- package/src/lib/db/remote-sql.ts +0 -62
- package/src/lib/db/sql.test.ts +0 -106
- package/src/lib/db/sql.ts +0 -192
- package/src/lib/hq-cloud-dep.smoke.test.ts +0 -75
- package/src/lib/local-tree-diff.test.ts +0 -301
- package/src/lib/local-tree-diff.ts +0 -362
- package/src/lib/narrow-hint-banner.test.ts +0 -235
- package/src/lib/narrow-hint-banner.ts +0 -212
- package/src/lib/plan-limit-nag.test.ts +0 -317
- package/src/lib/plan-limit-nag.ts +0 -264
- package/src/main.ts +0 -371
- package/src/node-preflight.test.ts +0 -60
- package/src/node-preflight.ts +0 -67
- package/src/outpost/session-heartbeat-bounds.test.ts +0 -195
- package/src/outpost/session-heartbeat-guard.test.ts +0 -105
- package/src/outpost/session-heartbeat-publisher.test.ts +0 -178
- package/src/outpost/session-heartbeat-publisher.ts +0 -186
- package/src/outpost/session-heartbeat-retain-guard.test.ts +0 -126
- package/src/outpost/session-heartbeat.test.ts +0 -459
- package/src/outpost/session-heartbeat.ts +0 -877
- package/src/packaging.test.ts +0 -109
- package/src/run/__fixtures__/discover-schemas/example.env.schema +0 -4
- package/src/run/discover-schemas.test.ts +0 -153
- package/src/run/discover-schemas.ts +0 -79
- package/src/run/hq-plugin.test.ts +0 -371
- package/src/run/hq-plugin.ts +0 -277
- package/src/run/varlock-shape.test.ts +0 -57
- package/src/schemas/hq-package.schema.json +0 -155
- package/src/sentry-before-send.test.ts +0 -359
- package/src/sentry-before-send.ts +0 -221
- package/src/sentry-epipe.test.ts +0 -37
- package/src/sentry-release.test.ts +0 -54
- package/src/sentry.test.ts +0 -24
- package/src/sentry.ts +0 -54
- package/src/strategies/link.ts +0 -62
- package/src/strategies/merge.ts +0 -142
- package/src/types.ts +0 -146
- package/src/utils/__fixtures__/scan-packages.generated-block.sh +0 -23
- package/src/utils/auth-error.test.ts +0 -40
- package/src/utils/auth-error.ts +0 -42
- package/src/utils/billing-gate.test.ts +0 -95
- package/src/utils/billing-gate.ts +0 -228
- package/src/utils/breadcrumb-buffer.ts +0 -18
- package/src/utils/cli-telemetry.test.ts +0 -153
- package/src/utils/cli-telemetry.ts +0 -61
- package/src/utils/cognito-session.machine.test.ts +0 -109
- package/src/utils/cognito-session.refresh.test.ts +0 -84
- package/src/utils/cognito-session.test.ts +0 -328
- package/src/utils/cognito-session.ts +0 -448
- package/src/utils/company-selection-error.test.ts +0 -42
- package/src/utils/company-selection-error.ts +0 -45
- package/src/utils/contribution-table.ts +0 -83
- package/src/utils/environmental-error.test.ts +0 -45
- package/src/utils/environmental-error.ts +0 -39
- package/src/utils/epipe.test.ts +0 -28
- package/src/utils/epipe.ts +0 -29
- package/src/utils/expected-cli-error.test.ts +0 -28
- package/src/utils/expected-cli-error.ts +0 -39
- package/src/utils/feedback-diagnostics.test.ts +0 -183
- package/src/utils/feedback-diagnostics.ts +0 -123
- package/src/utils/feedback-screenshots.test.ts +0 -134
- package/src/utils/feedback-screenshots.ts +0 -124
- package/src/utils/feedback-versions.test.ts +0 -98
- package/src/utils/feedback-versions.ts +0 -68
- package/src/utils/git.ts +0 -74
- package/src/utils/integrity.ts +0 -59
- package/src/utils/intercepted-process-exit.test.ts +0 -37
- package/src/utils/intercepted-process-exit.ts +0 -36
- package/src/utils/login-provider.test.ts +0 -42
- package/src/utils/login-provider.ts +0 -30
- package/src/utils/manifest.test.ts +0 -167
- package/src/utils/manifest.ts +0 -131
- package/src/utils/pack-contributions.test.ts +0 -603
- package/src/utils/pack-contributions.ts +0 -450
- package/src/utils/people.ts +0 -215
- package/src/utils/registry-client.ts +0 -204
- package/src/utils/registry.ts +0 -90
- package/src/utils/resolve-vault-credential.test.ts +0 -69
- package/src/utils/resolve-vault-credential.ts +0 -60
- package/src/utils/sandbox-runner-client.test.ts +0 -279
- package/src/utils/sandbox-runner-client.ts +0 -260
- package/src/utils/secrets-cache.ts +0 -183
- package/src/utils/sentry-identity.ts +0 -45
- package/src/utils/settle-with-timeout.test.ts +0 -21
- package/src/utils/settle-with-timeout.ts +0 -22
- package/src/utils/user-yaml-error.test.ts +0 -24
- package/src/utils/user-yaml-error.ts +0 -30
- package/src/utils/vault-api.test.ts +0 -536
- package/src/utils/vault-api.ts +0 -382
- package/src/utils/version-check.test.ts +0 -176
- package/src/utils/version-check.ts +0 -155
- package/src/utils/version-gate.test.ts +0 -1024
- package/src/utils/version-gate.ts +0 -702
- package/test/commands/db-tenant-isolation.test.ts +0 -94
- package/test/commands/db.test.ts +0 -85
- package/test/commands/signals.test.ts +0 -207
- package/test/commands/sources.test.ts +0 -231
- package/test/e2e/smoke-install-mcp.sh +0 -113
- package/test/fixtures/hq-pack-smoke-mcp/mcp/smoke-http.json +0 -1
- package/test/fixtures/hq-pack-smoke-mcp/package.yaml +0 -11
- package/test/fixtures/signals/action_item/sample.md +0 -16
- package/test/fixtures/signals/summary/sample.md +0 -12
- package/test/fixtures/sources/meetings/sample.md +0 -25
- package/test/helpers/cli-runner.ts +0 -150
- package/test/helpers/s3-list-mock.ts +0 -79
- package/test/helpers/vault-service-mock.ts +0 -295
- package/test/sources-signals/smoke.test.ts +0 -228
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -11
- package/vitest.e2e.config.ts +0 -7
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# detect-stale-core-policy-mirror.sh — find leftover core/policies/ copies of a
|
|
3
|
+
# personal/policies/ twin from before the personal→core policy mirror was
|
|
4
|
+
# retired.
|
|
5
|
+
#
|
|
6
|
+
# Background: HQ used to symlink personal/policies/<slug>.md into
|
|
7
|
+
# core/policies/<slug>.md at reindex time. That mirror is retired — the policy
|
|
8
|
+
# trigger hook (.claude/hooks/inject-policy-on-trigger.sh) now reads
|
|
9
|
+
# personal/policies/ DIRECTLY, ahead of core/policies/, and dedups by policy id.
|
|
10
|
+
# But an install upgraded across the retirement can still carry stale REGULAR
|
|
11
|
+
# FILE copies of personal policies inside core/policies/. Those copies are
|
|
12
|
+
# release-owned scaffold: /update-hq will not refresh them from personal/, and a
|
|
13
|
+
# copy that is missing the when:/on: trigger frontmatter its personal twin has
|
|
14
|
+
# gained (via migrate-policy-triggers.sh) can shadow or diverge from the live
|
|
15
|
+
# rule in confusing ways.
|
|
16
|
+
#
|
|
17
|
+
# This tool classifies every core/policies twin of a personal policy:
|
|
18
|
+
# identical — byte-identical to the personal twin: an orphaned mirror copy,
|
|
19
|
+
# behavior-neutral to remove (the personal twin still loads, and
|
|
20
|
+
# /update-hq restores any genuinely release-shipped file).
|
|
21
|
+
# symlink — a leftover personal→core mirror symlink: resolves back to
|
|
22
|
+
# personal, so also a behavior-neutral orphan.
|
|
23
|
+
# diverged — content differs from the personal twin. DRIFT CAN RUN EITHER
|
|
24
|
+
# DIRECTION (the core copy may be the newer, genericized one).
|
|
25
|
+
# These are NEVER pruned — a human must classify each.
|
|
26
|
+
#
|
|
27
|
+
# Modes:
|
|
28
|
+
# (default) human-readable report; always exit 0.
|
|
29
|
+
# --json machine-readable JSON array; exit 0.
|
|
30
|
+
# --check exit 0 if no twins, 1 if any twin exists (advisory gate).
|
|
31
|
+
# --prune-identical remove ONLY identical + leftover-symlink orphans; never
|
|
32
|
+
# touch diverged; print each action; exit 0.
|
|
33
|
+
#
|
|
34
|
+
# It never writes to personal/, never syncs personal→core, and never removes a
|
|
35
|
+
# core/policies file that has no personal twin (release-shipped policies are
|
|
36
|
+
# left untouched by construction).
|
|
37
|
+
|
|
38
|
+
set -euo pipefail
|
|
39
|
+
|
|
40
|
+
MODE="report"
|
|
41
|
+
case "${1:-}" in
|
|
42
|
+
"") MODE="report" ;;
|
|
43
|
+
--json) MODE="json" ;;
|
|
44
|
+
--check) MODE="check" ;;
|
|
45
|
+
--prune-identical) MODE="prune" ;;
|
|
46
|
+
-h|--help)
|
|
47
|
+
sed -n '2,45p' "$0"
|
|
48
|
+
exit 0 ;;
|
|
49
|
+
*)
|
|
50
|
+
printf 'detect-stale-core-policy-mirror: unknown argument: %s\n' "$1" >&2
|
|
51
|
+
printf 'usage: %s [--json|--check|--prune-identical]\n' "$0" >&2
|
|
52
|
+
exit 2 ;;
|
|
53
|
+
esac
|
|
54
|
+
|
|
55
|
+
# Resolve HQ root: explicit env wins, else the tree this script ships in.
|
|
56
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
57
|
+
HQ_ROOT="${HQ_ROOT:-${CLAUDE_PROJECT_DIR:-$(cd "$SCRIPT_DIR/../.." && pwd)}}"
|
|
58
|
+
|
|
59
|
+
PERSONAL_DIR="$HQ_ROOT/personal/policies"
|
|
60
|
+
CORE_DIR="$HQ_ROOT/core/policies"
|
|
61
|
+
|
|
62
|
+
# Collect twins into parallel arrays (bash 3.2-safe; no associative arrays).
|
|
63
|
+
names=()
|
|
64
|
+
statuses=()
|
|
65
|
+
|
|
66
|
+
if [ -d "$PERSONAL_DIR" ]; then
|
|
67
|
+
for pf in "$PERSONAL_DIR"/*.md; do
|
|
68
|
+
[ -e "$pf" ] || continue # no-glob-match guard (nullglob-free)
|
|
69
|
+
base="$(basename "$pf")"
|
|
70
|
+
case "$base" in example-policy.md|README.md) continue ;; esac
|
|
71
|
+
cf="$CORE_DIR/$base"
|
|
72
|
+
[ -e "$cf" ] || [ -L "$cf" ] || continue # no core twin → nothing to flag
|
|
73
|
+
if [ -L "$cf" ]; then
|
|
74
|
+
status="symlink"
|
|
75
|
+
elif cmp -s "$pf" "$cf"; then
|
|
76
|
+
status="identical"
|
|
77
|
+
else
|
|
78
|
+
status="diverged"
|
|
79
|
+
fi
|
|
80
|
+
names+=("$base")
|
|
81
|
+
statuses+=("$status")
|
|
82
|
+
done
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
total="${#names[@]}"
|
|
86
|
+
|
|
87
|
+
# Tally.
|
|
88
|
+
n_identical=0; n_symlink=0; n_diverged=0
|
|
89
|
+
i=0
|
|
90
|
+
while [ "$i" -lt "$total" ]; do
|
|
91
|
+
case "${statuses[$i]}" in
|
|
92
|
+
identical) n_identical=$((n_identical + 1)) ;;
|
|
93
|
+
symlink) n_symlink=$((n_symlink + 1)) ;;
|
|
94
|
+
diverged) n_diverged=$((n_diverged + 1)) ;;
|
|
95
|
+
esac
|
|
96
|
+
i=$((i + 1))
|
|
97
|
+
done
|
|
98
|
+
n_orphan=$((n_identical + n_symlink))
|
|
99
|
+
|
|
100
|
+
json_escape() { printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'; }
|
|
101
|
+
|
|
102
|
+
emit_json() {
|
|
103
|
+
printf '['
|
|
104
|
+
local i=0 sep=""
|
|
105
|
+
while [ "$i" -lt "$total" ]; do
|
|
106
|
+
printf '%s{"policy":"%s","status":"%s","core_path":"core/policies/%s","personal_path":"personal/policies/%s"}' \
|
|
107
|
+
"$sep" "$(json_escape "${names[$i]}")" "${statuses[$i]}" \
|
|
108
|
+
"$(json_escape "${names[$i]}")" "$(json_escape "${names[$i]}")"
|
|
109
|
+
sep=","
|
|
110
|
+
i=$((i + 1))
|
|
111
|
+
done
|
|
112
|
+
printf ']\n'
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
case "$MODE" in
|
|
116
|
+
json)
|
|
117
|
+
emit_json
|
|
118
|
+
exit 0 ;;
|
|
119
|
+
|
|
120
|
+
check)
|
|
121
|
+
if [ "$total" -eq 0 ]; then
|
|
122
|
+
echo "detect-stale-core-policy-mirror: clean (no core/policies twins of a personal policy)"
|
|
123
|
+
exit 0
|
|
124
|
+
fi
|
|
125
|
+
printf 'detect-stale-core-policy-mirror: %s twin(s) found — %s prunable orphan(s), %s diverged (need human classification)\n' \
|
|
126
|
+
"$total" "$n_orphan" "$n_diverged" >&2
|
|
127
|
+
exit 1 ;;
|
|
128
|
+
|
|
129
|
+
prune)
|
|
130
|
+
if [ "$total" -eq 0 ]; then
|
|
131
|
+
echo "detect-stale-core-policy-mirror: nothing to prune (no twins)"
|
|
132
|
+
exit 0
|
|
133
|
+
fi
|
|
134
|
+
removed=0; skipped=0
|
|
135
|
+
i=0
|
|
136
|
+
while [ "$i" -lt "$total" ]; do
|
|
137
|
+
base="${names[$i]}"; st="${statuses[$i]}"
|
|
138
|
+
case "$st" in
|
|
139
|
+
identical|symlink)
|
|
140
|
+
rm -f "$CORE_DIR/$base"
|
|
141
|
+
printf 'pruned (%s): core/policies/%s\n' "$st" "$base"
|
|
142
|
+
removed=$((removed + 1)) ;;
|
|
143
|
+
diverged)
|
|
144
|
+
printf 'SKIPPED (diverged, needs human classification): core/policies/%s\n' "$base"
|
|
145
|
+
skipped=$((skipped + 1)) ;;
|
|
146
|
+
esac
|
|
147
|
+
i=$((i + 1))
|
|
148
|
+
done
|
|
149
|
+
printf 'detect-stale-core-policy-mirror: pruned %s orphan(s); left %s diverged twin(s) for review.\n' \
|
|
150
|
+
"$removed" "$skipped"
|
|
151
|
+
exit 0 ;;
|
|
152
|
+
|
|
153
|
+
report|*)
|
|
154
|
+
if [ "$total" -eq 0 ]; then
|
|
155
|
+
echo "detect-stale-core-policy-mirror: clean — no core/policies copies shadow a personal twin."
|
|
156
|
+
exit 0
|
|
157
|
+
fi
|
|
158
|
+
printf 'Stale personal→core policy mirror copies in core/policies/ (%s twin(s)):\n\n' "$total"
|
|
159
|
+
if [ "$n_orphan" -gt 0 ]; then
|
|
160
|
+
printf 'Orphaned mirror copies (behavior-neutral — remove with --prune-identical):\n'
|
|
161
|
+
i=0
|
|
162
|
+
while [ "$i" -lt "$total" ]; do
|
|
163
|
+
case "${statuses[$i]}" in
|
|
164
|
+
identical) printf ' identical core/policies/%s\n' "${names[$i]}" ;;
|
|
165
|
+
symlink) printf ' symlink core/policies/%s\n' "${names[$i]}" ;;
|
|
166
|
+
esac
|
|
167
|
+
i=$((i + 1))
|
|
168
|
+
done
|
|
169
|
+
printf '\n'
|
|
170
|
+
fi
|
|
171
|
+
if [ "$n_diverged" -gt 0 ]; then
|
|
172
|
+
printf 'Diverged copies (DRIFT MAY RUN EITHER DIRECTION — classify by hand, do NOT blind-sync):\n'
|
|
173
|
+
i=0
|
|
174
|
+
while [ "$i" -lt "$total" ]; do
|
|
175
|
+
if [ "${statuses[$i]}" = "diverged" ]; then
|
|
176
|
+
printf ' diverged core/policies/%s (diff: git diff --no-index personal/policies/%s core/policies/%s)\n' \
|
|
177
|
+
"${names[$i]}" "${names[$i]}" "${names[$i]}"
|
|
178
|
+
fi
|
|
179
|
+
i=$((i + 1))
|
|
180
|
+
done
|
|
181
|
+
printf '\n'
|
|
182
|
+
fi
|
|
183
|
+
printf 'Summary: %s orphan(s), %s diverged.\n' "$n_orphan" "$n_diverged"
|
|
184
|
+
exit 0 ;;
|
|
185
|
+
esac
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# hq-status-summary.sh — classify noisy HQ git status output.
|
|
3
|
+
#
|
|
4
|
+
# This is deliberately read-only. It turns `git status --porcelain --ignored`
|
|
5
|
+
# into a compact summary that separates current-session touched paths from the
|
|
6
|
+
# local HQ baseline noise that may remain untracked on installer-created roots.
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
ROOT="${HQ_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
11
|
+
PORCELAIN_FILE=""
|
|
12
|
+
SESSION_FILES_JSON="[]"
|
|
13
|
+
SESSION_FILES_FILE=""
|
|
14
|
+
FORMAT="json"
|
|
15
|
+
|
|
16
|
+
while [[ $# -gt 0 ]]; do
|
|
17
|
+
case "$1" in
|
|
18
|
+
--porcelain-file) PORCELAIN_FILE="$2"; shift 2 ;;
|
|
19
|
+
--session-files-json) SESSION_FILES_JSON="$2"; shift 2 ;;
|
|
20
|
+
--session-files-file) SESSION_FILES_FILE="$2"; shift 2 ;;
|
|
21
|
+
--json) FORMAT="json"; shift ;;
|
|
22
|
+
--text) FORMAT="text"; shift ;;
|
|
23
|
+
*) echo "Unknown arg: $1" >&2; exit 2 ;;
|
|
24
|
+
esac
|
|
25
|
+
done
|
|
26
|
+
|
|
27
|
+
# --session-files-file <path> keeps a large session changeset off argv (mirrors
|
|
28
|
+
# the existing --porcelain-file pattern). A missing path is a hard error rather
|
|
29
|
+
# than a silent empty summary. It is resolved before the cd below because a
|
|
30
|
+
# relative path is relative to the caller's cwd, not ROOT.
|
|
31
|
+
if [[ -n "$SESSION_FILES_FILE" ]]; then
|
|
32
|
+
if [[ ! -f "$SESSION_FILES_FILE" ]]; then
|
|
33
|
+
echo "hq-status-summary: --session-files-file not found: $SESSION_FILES_FILE" >&2
|
|
34
|
+
exit 3
|
|
35
|
+
fi
|
|
36
|
+
case "$SESSION_FILES_FILE" in
|
|
37
|
+
/*) : ;;
|
|
38
|
+
*) SESSION_FILES_FILE="$PWD/$SESSION_FILES_FILE" ;;
|
|
39
|
+
esac
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
cd "$ROOT"
|
|
43
|
+
|
|
44
|
+
tmpdir=$(mktemp -d)
|
|
45
|
+
trap 'rm -rf "$tmpdir"' EXIT
|
|
46
|
+
|
|
47
|
+
tracked="$tmpdir/tracked"
|
|
48
|
+
session_untracked="$tmpdir/session_untracked"
|
|
49
|
+
baseline_untracked="$tmpdir/baseline_untracked"
|
|
50
|
+
unrelated_untracked="$tmpdir/unrelated_untracked"
|
|
51
|
+
ignored="$tmpdir/ignored"
|
|
52
|
+
unsafe_session="$tmpdir/unsafe_session"
|
|
53
|
+
: >"$tracked"; : >"$session_untracked"; : >"$baseline_untracked"; : >"$unrelated_untracked"; : >"$ignored"; : >"$unsafe_session"
|
|
54
|
+
|
|
55
|
+
session_paths_file="$tmpdir/session_paths"
|
|
56
|
+
session_files_jq='
|
|
57
|
+
if type != "array" then empty else
|
|
58
|
+
.[] | if type == "string" then . else (.path // empty) end
|
|
59
|
+
end
|
|
60
|
+
'
|
|
61
|
+
if [[ -n "$SESSION_FILES_FILE" ]]; then
|
|
62
|
+
jq -r "$session_files_jq" "$SESSION_FILES_FILE" 2>/dev/null | sed '/^$/d' > "$session_paths_file" || : > "$session_paths_file"
|
|
63
|
+
else
|
|
64
|
+
printf '%s' "$SESSION_FILES_JSON" | jq -r "$session_files_jq" 2>/dev/null | sed '/^$/d' > "$session_paths_file" || : > "$session_paths_file"
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
baseline_patterns_file="$tmpdir/baseline_patterns"
|
|
68
|
+
if [[ -f workspace/baseline/hq-local-baseline.json ]]; then
|
|
69
|
+
jq -r '.categories[]?.patterns[]?' workspace/baseline/hq-local-baseline.json 2>/dev/null > "$baseline_patterns_file" || : > "$baseline_patterns_file"
|
|
70
|
+
else
|
|
71
|
+
cat > "$baseline_patterns_file" <<'PATTERNS'
|
|
72
|
+
companies/*
|
|
73
|
+
workspace/*
|
|
74
|
+
repos/*
|
|
75
|
+
core/settings/*
|
|
76
|
+
.hq/*
|
|
77
|
+
PATTERNS
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
is_session_path() {
|
|
81
|
+
local path="$1"
|
|
82
|
+
local p
|
|
83
|
+
while IFS= read -r p; do
|
|
84
|
+
[[ -z "$p" ]] && continue
|
|
85
|
+
if [[ "$path" == "$p" || "$path" == "$p/"* || "$p" == "$path/"* ]]; then
|
|
86
|
+
return 0
|
|
87
|
+
fi
|
|
88
|
+
done < "$session_paths_file"
|
|
89
|
+
return 1
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
is_baseline_path() {
|
|
93
|
+
local path="$1"
|
|
94
|
+
local pat
|
|
95
|
+
while IFS= read -r pat; do
|
|
96
|
+
[[ -z "$pat" ]] && continue
|
|
97
|
+
case "$path" in
|
|
98
|
+
$pat) return 0 ;;
|
|
99
|
+
esac
|
|
100
|
+
done < "$baseline_patterns_file"
|
|
101
|
+
return 1
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
status_source="$tmpdir/porcelain"
|
|
105
|
+
if [[ -n "$PORCELAIN_FILE" ]]; then
|
|
106
|
+
cp "$PORCELAIN_FILE" "$status_source"
|
|
107
|
+
else
|
|
108
|
+
git status --porcelain --ignored > "$status_source"
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
while IFS= read -r line; do
|
|
112
|
+
[[ -z "$line" ]] && continue
|
|
113
|
+
code="${line:0:2}"
|
|
114
|
+
path="${line:3}"
|
|
115
|
+
if [[ "$path" == *" -> "* ]]; then
|
|
116
|
+
path="${path##* -> }"
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
case "$code" in
|
|
120
|
+
'!!')
|
|
121
|
+
printf '%s\n' "$path" >> "$ignored"
|
|
122
|
+
;;
|
|
123
|
+
'??')
|
|
124
|
+
if is_session_path "$path"; then
|
|
125
|
+
printf '%s\n' "$path" >> "$session_untracked"
|
|
126
|
+
elif is_baseline_path "$path"; then
|
|
127
|
+
printf '%s\n' "$path" >> "$baseline_untracked"
|
|
128
|
+
else
|
|
129
|
+
printf '%s\n' "$path" >> "$unrelated_untracked"
|
|
130
|
+
fi
|
|
131
|
+
;;
|
|
132
|
+
*)
|
|
133
|
+
printf '%s\n' "$path" >> "$tracked"
|
|
134
|
+
;;
|
|
135
|
+
esac
|
|
136
|
+
done < "$status_source"
|
|
137
|
+
|
|
138
|
+
# Materialize each classified list as a JSON array file, then feed jq via
|
|
139
|
+
# --slurpfile. --argjson puts the whole array on argv and overflows CreateProcess
|
|
140
|
+
# (~32KB) / MAX_ARG_STRLEN (128KB) when thousands of untracked paths classify here.
|
|
141
|
+
write_json_array() {
|
|
142
|
+
jq -R -s 'split("\n") | map(select(length > 0))' "$1" > "$2"
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
tracked_json="$tmpdir/tracked.json"
|
|
146
|
+
session_untracked_json="$tmpdir/session_untracked.json"
|
|
147
|
+
baseline_untracked_json="$tmpdir/baseline_untracked.json"
|
|
148
|
+
unrelated_untracked_json="$tmpdir/unrelated_untracked.json"
|
|
149
|
+
ignored_json="$tmpdir/ignored.json"
|
|
150
|
+
|
|
151
|
+
write_json_array "$tracked" "$tracked_json"
|
|
152
|
+
write_json_array "$session_untracked" "$session_untracked_json"
|
|
153
|
+
write_json_array "$baseline_untracked" "$baseline_untracked_json"
|
|
154
|
+
write_json_array "$unrelated_untracked" "$unrelated_untracked_json"
|
|
155
|
+
write_json_array "$ignored" "$ignored_json"
|
|
156
|
+
|
|
157
|
+
if [[ "$FORMAT" == "text" ]]; then
|
|
158
|
+
printf 'tracked_changes=%s\n' "$(jq 'length' "$tracked_json")"
|
|
159
|
+
printf 'session_untracked=%s\n' "$(jq 'length' "$session_untracked_json")"
|
|
160
|
+
printf 'baseline_untracked=%s\n' "$(jq 'length' "$baseline_untracked_json")"
|
|
161
|
+
printf 'unrelated_untracked=%s\n' "$(jq 'length' "$unrelated_untracked_json")"
|
|
162
|
+
printf 'ignored=%s\n' "$(jq 'length' "$ignored_json")"
|
|
163
|
+
else
|
|
164
|
+
jq -n \
|
|
165
|
+
--slurpfile tracked "$tracked_json" \
|
|
166
|
+
--slurpfile session_untracked "$session_untracked_json" \
|
|
167
|
+
--slurpfile baseline_untracked "$baseline_untracked_json" \
|
|
168
|
+
--slurpfile unrelated_untracked "$unrelated_untracked_json" \
|
|
169
|
+
--slurpfile ignored "$ignored_json" \
|
|
170
|
+
'{
|
|
171
|
+
counts: {
|
|
172
|
+
tracked_changes: ($tracked[0] | length),
|
|
173
|
+
session_touched_untracked: ($session_untracked[0] | length),
|
|
174
|
+
baseline_untracked: ($baseline_untracked[0] | length),
|
|
175
|
+
unrelated_untracked: ($unrelated_untracked[0] | length),
|
|
176
|
+
ignored: ($ignored[0] | length),
|
|
177
|
+
baseline_noise: (($baseline_untracked[0] | length) + ($unrelated_untracked[0] | length))
|
|
178
|
+
},
|
|
179
|
+
files: {
|
|
180
|
+
tracked_changes: $tracked[0],
|
|
181
|
+
session_touched_untracked: $session_untracked[0],
|
|
182
|
+
baseline_untracked: $baseline_untracked[0],
|
|
183
|
+
unrelated_untracked: $unrelated_untracked[0],
|
|
184
|
+
ignored: $ignored[0]
|
|
185
|
+
}
|
|
186
|
+
}'
|
|
187
|
+
fi
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# hq-core: public
|
|
3
|
+
# Detect README team-table names that no longer match ontology person entities.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
usage() {
|
|
7
|
+
echo "USAGE: ontology-readme-drift.sh <company-slug> [--root <HQ_ROOT>]" >&2
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
SLUG=""
|
|
11
|
+
ROOT_ARG=""
|
|
12
|
+
|
|
13
|
+
while [ "$#" -gt 0 ]; do
|
|
14
|
+
case "$1" in
|
|
15
|
+
--root)
|
|
16
|
+
if [ "$#" -lt 2 ]; then
|
|
17
|
+
usage
|
|
18
|
+
exit 2
|
|
19
|
+
fi
|
|
20
|
+
ROOT_ARG="$2"
|
|
21
|
+
shift 2
|
|
22
|
+
;;
|
|
23
|
+
-*)
|
|
24
|
+
usage
|
|
25
|
+
exit 2
|
|
26
|
+
;;
|
|
27
|
+
*)
|
|
28
|
+
if [ -n "$SLUG" ]; then
|
|
29
|
+
usage
|
|
30
|
+
exit 2
|
|
31
|
+
fi
|
|
32
|
+
SLUG="$1"
|
|
33
|
+
shift
|
|
34
|
+
;;
|
|
35
|
+
esac
|
|
36
|
+
done
|
|
37
|
+
|
|
38
|
+
if [ -z "$SLUG" ]; then
|
|
39
|
+
usage
|
|
40
|
+
exit 2
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
if [ -n "$ROOT_ARG" ]; then
|
|
44
|
+
ROOT="$ROOT_ARG"
|
|
45
|
+
elif [ -n "${HQ_ROOT:-}" ]; then
|
|
46
|
+
ROOT="$HQ_ROOT"
|
|
47
|
+
elif [ -n "${CLAUDE_PROJECT_DIR:-}" ]; then
|
|
48
|
+
ROOT="$CLAUDE_PROJECT_DIR"
|
|
49
|
+
else
|
|
50
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
51
|
+
ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
README="$ROOT/companies/$SLUG/README.md"
|
|
55
|
+
PERSONS_DIR="$ROOT/companies/$SLUG/ontology/entities/person"
|
|
56
|
+
|
|
57
|
+
if [ ! -f "$README" ]; then
|
|
58
|
+
echo "ERROR: README missing: companies/$SLUG/README.md" >&2
|
|
59
|
+
exit 2
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
if [ ! -d "$PERSONS_DIR" ]; then
|
|
63
|
+
echo "ERROR: ontology person directory missing: companies/$SLUG/ontology/entities/person" >&2
|
|
64
|
+
exit 2
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
has_person_file=0
|
|
68
|
+
for person_file in "$PERSONS_DIR"/*.md; do
|
|
69
|
+
[ -f "$person_file" ] || continue
|
|
70
|
+
has_person_file=1
|
|
71
|
+
break
|
|
72
|
+
done
|
|
73
|
+
|
|
74
|
+
if [ "$has_person_file" -eq 0 ]; then
|
|
75
|
+
echo "ERROR: ontology person directory is empty: companies/$SLUG/ontology/entities/person" >&2
|
|
76
|
+
exit 2
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
trim_name_awk='
|
|
80
|
+
function trim(s) {
|
|
81
|
+
sub(/^[[:space:]]+/, "", s)
|
|
82
|
+
sub(/[[:space:]]+$/, "", s)
|
|
83
|
+
return s
|
|
84
|
+
}
|
|
85
|
+
'
|
|
86
|
+
|
|
87
|
+
extract_team_names() {
|
|
88
|
+
# Parse only the first markdown table after ## Team; column 2 is Name.
|
|
89
|
+
awk "$trim_name_awk"'
|
|
90
|
+
/^##[[:space:]]+Team[[:space:]]*$/ {
|
|
91
|
+
in_team = 1
|
|
92
|
+
next
|
|
93
|
+
}
|
|
94
|
+
in_team && /^##[[:space:]]+/ {
|
|
95
|
+
exit
|
|
96
|
+
}
|
|
97
|
+
in_team {
|
|
98
|
+
if ($0 ~ /^[[:space:]]*\|/) {
|
|
99
|
+
in_table = 1
|
|
100
|
+
row += 1
|
|
101
|
+
if (row <= 2) {
|
|
102
|
+
next
|
|
103
|
+
}
|
|
104
|
+
line = $0
|
|
105
|
+
sub(/^[[:space:]]*/, "", line)
|
|
106
|
+
split(line, cols, /\|/)
|
|
107
|
+
name = cols[3]
|
|
108
|
+
gsub(/<!--[^>]*-->/, "", name)
|
|
109
|
+
name = trim(name)
|
|
110
|
+
if (name != "") {
|
|
111
|
+
print name
|
|
112
|
+
}
|
|
113
|
+
next
|
|
114
|
+
}
|
|
115
|
+
if (in_table) {
|
|
116
|
+
exit
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
' "$README"
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
extract_person_names() {
|
|
123
|
+
# Read canonical_name and aliases from YAML frontmatter only.
|
|
124
|
+
awk "$trim_name_awk"'
|
|
125
|
+
function unquote(s) {
|
|
126
|
+
s = trim(s)
|
|
127
|
+
if ((s ~ /^".*"$/) || (s ~ /^'\''.*'\''$/)) {
|
|
128
|
+
s = substr(s, 2, length(s) - 2)
|
|
129
|
+
}
|
|
130
|
+
return s
|
|
131
|
+
}
|
|
132
|
+
/^---[[:space:]]*$/ {
|
|
133
|
+
markers += 1
|
|
134
|
+
if (markers == 1) {
|
|
135
|
+
in_frontmatter = 1
|
|
136
|
+
next
|
|
137
|
+
}
|
|
138
|
+
if (markers == 2) {
|
|
139
|
+
exit
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
!in_frontmatter {
|
|
143
|
+
next
|
|
144
|
+
}
|
|
145
|
+
/^[[:space:]]*aliases:[[:space:]]*$/ {
|
|
146
|
+
in_aliases = 1
|
|
147
|
+
next
|
|
148
|
+
}
|
|
149
|
+
in_aliases && /^[[:space:]]*-[[:space:]]*/ {
|
|
150
|
+
line = $0
|
|
151
|
+
sub(/^[[:space:]]*-[[:space:]]*/, "", line)
|
|
152
|
+
line = unquote(line)
|
|
153
|
+
if (line != "") {
|
|
154
|
+
print line
|
|
155
|
+
}
|
|
156
|
+
next
|
|
157
|
+
}
|
|
158
|
+
in_aliases && $0 !~ /^[[:space:]]*($|#)/ {
|
|
159
|
+
in_aliases = 0
|
|
160
|
+
}
|
|
161
|
+
/^[[:space:]]*canonical_name[[:space:]]*:/ {
|
|
162
|
+
line = $0
|
|
163
|
+
sub(/^[[:space:]]*canonical_name[[:space:]]*:[[:space:]]*/, "", line)
|
|
164
|
+
line = unquote(line)
|
|
165
|
+
if (line != "") {
|
|
166
|
+
print line
|
|
167
|
+
}
|
|
168
|
+
next
|
|
169
|
+
}
|
|
170
|
+
' "$1"
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
normalize_name() {
|
|
174
|
+
LC_ALL=C awk -v s="$1" 'BEGIN {
|
|
175
|
+
s = tolower(s)
|
|
176
|
+
gsub(/[^[:alnum:][:space:]]/, "", s)
|
|
177
|
+
gsub(/[[:space:]]+/, " ", s)
|
|
178
|
+
sub(/^ /, "", s)
|
|
179
|
+
sub(/ $/, "", s)
|
|
180
|
+
print s
|
|
181
|
+
}'
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
contains_token() {
|
|
185
|
+
token="$1"
|
|
186
|
+
shift
|
|
187
|
+
for candidate in "$@"; do
|
|
188
|
+
if [ "$candidate" = "$token" ]; then
|
|
189
|
+
return 0
|
|
190
|
+
fi
|
|
191
|
+
done
|
|
192
|
+
return 1
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
name_matches() {
|
|
196
|
+
readme_norm="$1"
|
|
197
|
+
ontology_norm="$2"
|
|
198
|
+
|
|
199
|
+
if [ "$readme_norm" = "$ontology_norm" ]; then
|
|
200
|
+
return 0
|
|
201
|
+
fi
|
|
202
|
+
if [ -z "$readme_norm" ] || [ -z "$ontology_norm" ]; then
|
|
203
|
+
return 1
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
IFS=' ' read -r -a readme_tokens <<< "$readme_norm"
|
|
207
|
+
IFS=' ' read -r -a ontology_tokens <<< "$ontology_norm"
|
|
208
|
+
|
|
209
|
+
if [ "${#readme_tokens[@]}" -lt "${#ontology_tokens[@]}" ] ||
|
|
210
|
+
{ [ "${#readme_tokens[@]}" -eq "${#ontology_tokens[@]}" ] && [ "${#readme_norm}" -le "${#ontology_norm}" ]; }; then
|
|
211
|
+
for token in "${readme_tokens[@]}"; do
|
|
212
|
+
contains_token "$token" "${ontology_tokens[@]}" || return 1
|
|
213
|
+
done
|
|
214
|
+
else
|
|
215
|
+
for token in "${ontology_tokens[@]}"; do
|
|
216
|
+
contains_token "$token" "${readme_tokens[@]}" || return 1
|
|
217
|
+
done
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
return 0
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
ontology_names=()
|
|
224
|
+
for person_file in "$PERSONS_DIR"/*.md; do
|
|
225
|
+
[ -f "$person_file" ] || continue
|
|
226
|
+
while IFS= read -r person_name; do
|
|
227
|
+
person_norm="$(normalize_name "$person_name")"
|
|
228
|
+
[ -n "$person_norm" ] || continue
|
|
229
|
+
ontology_names+=("$person_norm")
|
|
230
|
+
done < <(extract_person_names "$person_file")
|
|
231
|
+
done
|
|
232
|
+
|
|
233
|
+
if [ "${#ontology_names[@]}" -eq 0 ]; then
|
|
234
|
+
echo "ERROR: ontology person directory has no canonical_name or aliases to compare: companies/$SLUG/ontology/entities/person" >&2
|
|
235
|
+
exit 2
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
drift_count=0
|
|
239
|
+
while IFS= read -r team_name; do
|
|
240
|
+
team_norm="$(normalize_name "$team_name")"
|
|
241
|
+
[ -n "$team_norm" ] || continue
|
|
242
|
+
|
|
243
|
+
matched=0
|
|
244
|
+
for ontology_name in "${ontology_names[@]}"; do
|
|
245
|
+
if name_matches "$team_norm" "$ontology_name"; then
|
|
246
|
+
matched=1
|
|
247
|
+
break
|
|
248
|
+
fi
|
|
249
|
+
done
|
|
250
|
+
|
|
251
|
+
if [ "$matched" -eq 0 ]; then
|
|
252
|
+
echo "DRIFT: \"$team_name\" in companies/$SLUG README team table has no matching active ontology person entity (possibly departed or misspelled)"
|
|
253
|
+
drift_count=$((drift_count + 1))
|
|
254
|
+
fi
|
|
255
|
+
done < <(extract_team_names)
|
|
256
|
+
|
|
257
|
+
if [ "$drift_count" -eq 0 ]; then
|
|
258
|
+
echo "ontology-readme-drift($SLUG): no drift"
|
|
259
|
+
fi
|
|
260
|
+
echo "ontology-readme-drift($SLUG): $drift_count drift(s)"
|
|
261
|
+
|
|
262
|
+
if [ "$drift_count" -gt 0 ]; then
|
|
263
|
+
exit 1
|
|
264
|
+
fi
|