deepdebug-local-agent 1.0.22 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/.claude/context.md +76 -0
  2. package/.claude/skills/README.md +25 -0
  3. package/.claude/skills/tdd-debugging/SKILL.md +53 -0
  4. package/.claude/skills/tdd-debugging/references/anti-patterns.md +43 -0
  5. package/.claude/skills/tdd-debugging/references/command-reference.md +46 -0
  6. package/.claude/standards/README.md +51 -0
  7. package/.claude/standards/clean-code.md +75 -0
  8. package/.claude/standards/code-review-checklist.md +61 -0
  9. package/.claude/standards/layered-architecture.md +163 -0
  10. package/.claude/standards/node.md +45 -0
  11. package/.claude/standards/security-owasp.md +114 -0
  12. package/.claude/standards/simplicity.md +42 -0
  13. package/.claude/standards/solid.md +64 -0
  14. package/.claude/standards/testing.md +59 -0
  15. package/.cursor/rules/standards.mdc +17 -0
  16. package/.github/workflows/build-byoc-image.yml +315 -0
  17. package/.github/workflows/deploy-gcp.yml +102 -0
  18. package/.github/workflows/npm-publish.yml +69 -8
  19. package/AGENTS.md +21 -0
  20. package/CHANGELOG.md +69 -0
  21. package/CLAUDE.md +13 -0
  22. package/CLAUDE_CODE_AGENT_INFINITE.md +5 -3
  23. package/Dockerfile +4 -0
  24. package/Dockerfile.full +55 -3
  25. package/README.md +81 -0
  26. package/bin/install.js +16 -5
  27. package/cloudbuild-full.yaml +57 -0
  28. package/docker-compose.yml +5 -3
  29. package/docs/OBSERVABILITY.md +164 -0
  30. package/docs/byoc-agent-image.md +70 -0
  31. package/docs/graphify-packaging-plan.md +133 -0
  32. package/docs/graphify-validation-run-2026-10-28.md +243 -0
  33. package/docs/legacy-connectors.md +268 -0
  34. package/docs/releasing.md +43 -0
  35. package/docs/signed-commands.md +163 -0
  36. package/env.example +30 -0
  37. package/helm/Chart.yaml +2 -2
  38. package/helm/templates/deployment.yaml +13 -3
  39. package/helm/templates/networkpolicy.yaml +22 -0
  40. package/helm/templates/secret.yaml +1 -0
  41. package/helm/values.yaml +13 -14
  42. package/mcp-server.js +4 -1
  43. package/package.json +12 -4
  44. package/scripts/sync-fixtures.mjs +246 -0
  45. package/scripts/upgrade-observability-push.sh +227 -0
  46. package/src/adapters/as400/as400-parser.js +12 -0
  47. package/src/adapters/as400/index.js +134 -0
  48. package/src/adapters/cobol/cobol-parser.js +37 -0
  49. package/src/adapters/cobol/index.js +151 -0
  50. package/src/adapters/ibmmq/ibmmq-parser.js +23 -0
  51. package/src/adapters/ibmmq/index.js +137 -0
  52. package/src/adapters/index.js +79 -0
  53. package/src/adapters/oracle-forms/index.js +145 -0
  54. package/src/adapters/oracle-forms/oracle-forms-parser.js +11 -0
  55. package/src/adapters/sap/index.js +146 -0
  56. package/src/adapters/sap/sap-parser.js +41 -0
  57. package/src/ai-engine.js +22 -22
  58. package/src/config/trusted-signing-keys.json +3 -0
  59. package/src/detectors/build-tool-detector.js +50 -0
  60. package/src/detectors/multi-project-detector.js +22 -22
  61. package/src/detectors/service-detector.js +24 -24
  62. package/src/exec-utils.js +26 -4
  63. package/src/git/base-git-provider.js +117 -117
  64. package/src/git/bitbucket-provider.js +2 -2
  65. package/src/git/git-provider-registry.js +18 -18
  66. package/src/git/github-provider.js +13 -13
  67. package/src/git/multi-repo-pr.js +161 -0
  68. package/src/mcp-http-server.js +32 -16
  69. package/src/observability/bundle-files.js +418 -0
  70. package/src/observability/detect.js +151 -0
  71. package/src/observability/provision.js +212 -0
  72. package/src/observability/render-cli.js +59 -0
  73. package/src/observability/routes.js +88 -0
  74. package/src/patch/patch-engine.js +28 -28
  75. package/src/patch-manager.js +85 -85
  76. package/src/patch_bkp.js +14 -14
  77. package/src/ports.js +4 -4
  78. package/src/routes/workspace.route.js +47 -16
  79. package/src/runtimes/base-runtime.js +75 -75
  80. package/src/runtimes/java/gradle-runtime.js +17 -17
  81. package/src/runtimes/java/java-integrations.js +25 -25
  82. package/src/runtimes/java/maven-runtime.js +34 -34
  83. package/src/runtimes/node/node-integrations.js +1 -1
  84. package/src/runtimes/node/npm-runtime.js +14 -14
  85. package/src/runtimes/node/yarn-runtime.js +4 -4
  86. package/src/runtimes/runtime-registry.js +25 -25
  87. package/src/server-local.js +4 -4
  88. package/src/server.js +1412 -335
  89. package/src/siblings/burst-suppressor.js +89 -0
  90. package/src/siblings/capabilities.js +273 -0
  91. package/src/siblings/env-compat.cjs +58 -0
  92. package/src/siblings/incident-forwarder.js +320 -0
  93. package/src/siblings/logger.js +15 -0
  94. package/src/siblings/metrics.js +94 -0
  95. package/src/utils/agent-auth.js +184 -0
  96. package/src/utils/approval-token.js +56 -0
  97. package/src/utils/bind-host.js +29 -0
  98. package/src/utils/branch-refspec.js +98 -0
  99. package/src/utils/child-env.js +34 -0
  100. package/src/utils/environment-diagnostics.js +55 -55
  101. package/src/utils/exec-policy.js +962 -0
  102. package/src/utils/exec-utils.js +15 -15
  103. package/src/utils/file-count.js +46 -0
  104. package/src/utils/fs-utils.js +10 -10
  105. package/src/utils/multi-repo-open.js +132 -0
  106. package/src/utils/open-gate.js +183 -0
  107. package/src/utils/ranged-read.js +65 -0
  108. package/src/utils/request-guards.js +77 -0
  109. package/src/utils/request-signing.js +342 -0
  110. package/src/utils/root-recovery.js +244 -0
  111. package/src/utils/safe-exec-args.js +58 -0
  112. package/src/utils/sandbox.js +182 -0
  113. package/src/utils/search-outcome.js +56 -0
  114. package/src/utils/workspace-root.js +80 -0
  115. package/src/validators/syntax-validator.js +149 -0
  116. package/src/workspace/detect-port.js +16 -16
  117. package/src/workspace/file-reader.js +1 -1
  118. package/src/workspace/process-manager.js +46 -46
  119. package/src/workspace-manager.js +86 -24
  120. package/test/adapters/as400/as400.test.js +121 -0
  121. package/test/adapters/cobol/cobol.test.js +217 -0
  122. package/test/adapters/ibmmq/ibmmq.test.js +122 -0
  123. package/test/adapters/oracle-forms/oracle-forms.test.js +180 -0
  124. package/test/adapters/registry.test.js +108 -0
  125. package/test/adapters/sap/sap.test.js +142 -0
  126. package/test/agent-auth.test.js +194 -0
  127. package/test/bind-host.test.js +26 -0
  128. package/test/branch-refspec.test.js +224 -0
  129. package/test/burst-suppressor.test.js +60 -0
  130. package/test/capabilities-access.test.js +47 -0
  131. package/test/capabilities.test.js +87 -0
  132. package/test/child-env.test.js +23 -0
  133. package/test/env-compat.test.js +71 -0
  134. package/test/exec-policy.test.js +463 -0
  135. package/test/file-count.test.js +64 -0
  136. package/test/incident-forwarder.test.js +231 -0
  137. package/test/multi-repo-open.test.js +180 -0
  138. package/test/multi-repo-pr.test.js +129 -0
  139. package/test/observability-detect.test.js +199 -0
  140. package/test/open-gate.test.js +341 -0
  141. package/test/ranged-read.test.js +60 -0
  142. package/test/request-guards.test.js +91 -0
  143. package/test/request-signing.test.js +284 -0
  144. package/test/root-recovery-sidecar.test.js +171 -0
  145. package/test/root-recovery.test.js +274 -0
  146. package/test/safe-exec-args.test.js +58 -0
  147. package/test/sandbox.test.js +98 -0
  148. package/test/search-outcome.test.js +50 -0
  149. package/test/workspace-root.test.js +64 -0
  150. package/tools/graphify-validation/README.md +92 -0
  151. package/tools/graphify-validation/config/questions.json +31 -0
  152. package/tools/graphify-validation/config/repos.json +9 -0
  153. package/tools/graphify-validation/config/symbols.json +6 -0
  154. package/tools/graphify-validation/fixtures/spring/expected.json +34 -0
  155. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/AppConfig.java +29 -0
  156. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/AuditLogger.java +15 -0
  157. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/Cache.java +7 -0
  158. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/CheckoutService.java +25 -0
  159. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/InMemoryCache.java +18 -0
  160. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/OrderController.java +22 -0
  161. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/PaymentGateway.java +5 -0
  162. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/PaypalGateway.java +11 -0
  163. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/RefundService.java +14 -0
  164. package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/StripeGateway.java +11 -0
  165. package/tools/graphify-validation/lib/affected_vs_rg.py +81 -0
  166. package/tools/graphify-validation/lib/audit_hook/sitecustomize.py +22 -0
  167. package/tools/graphify-validation/lib/graph_metrics.py +96 -0
  168. package/tools/graphify-validation/lib/gv_common.py +130 -0
  169. package/tools/graphify-validation/lib/licenses.py +47 -0
  170. package/tools/graphify-validation/lib/pip_audit_summary.py +18 -0
  171. package/tools/graphify-validation/lib/recall.py +115 -0
  172. package/tools/graphify-validation/lib/report.py +226 -0
  173. package/tools/graphify-validation/lib/run_all.py +237 -0
  174. package/tools/graphify-validation/lib/run_graphify.py +144 -0
  175. package/tools/graphify-validation/lib/rusage_helper.py +17 -0
  176. package/tools/graphify-validation/lib/sample_edges.py +246 -0
  177. package/tools/graphify-validation/lib/spring_fixture.py +81 -0
  178. package/tools/graphify-validation/requirements-py311.lock +508 -0
  179. package/tools/graphify-validation/requirements-tools.in +1 -0
  180. package/tools/graphify-validation/requirements-tools.lock +371 -0
  181. package/tools/graphify-validation/requirements.in +1 -0
  182. package/tools/graphify-validation/requirements.lock +502 -0
  183. package/tools/graphify-validation/run.sh +43 -0
  184. package/tools/graphify-validation/tests/test_harness.py +151 -0
  185. package/cloudbuild-agent-qa.yaml +0 -43
  186. package/cloudbuild-agent-staging.yaml +0 -40
  187. package/cloudbuild.yaml.deprecated +0 -42
  188. package/src/local-agent-source.tar.gz +0 -0
@@ -0,0 +1,47 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { isAuthorized, publicCapabilities, fullCapabilities } from '../src/siblings/capabilities.js';
4
+
5
+ const MANIFEST = {
6
+ mode: 'SPLIT_PLANE', reason: 'partial local capability',
7
+ source_available: false, toolchains: ['git', 'python3'],
8
+ container_runtime: 'docker', docker_sock: false, egress: { gateway: true },
9
+ build_capable: false, test_capable: false, runtimes: [],
10
+ };
11
+ const STATUS = { contract_ok: false, contract_preflight: { ok: false, reason: 'missing_internal_token' }, stats: {} };
12
+
13
+ const SENSITIVE = ['toolchains', 'container_runtime', 'docker_sock', 'egress', 'source_available'];
14
+
15
+ test('unauthenticated view exposes health only, no topology inventory', () => {
16
+ const v = publicCapabilities(MANIFEST, STATUS, 42);
17
+ assert.equal(v.authenticated, false);
18
+ assert.equal(v.mode, 'SPLIT_PLANE');
19
+ assert.equal(v.contract.ok, false);
20
+ assert.equal(v.contract.preflight_reason, 'missing_internal_token'); // the C1 signal
21
+ assert.equal(v.uptime_s, 42);
22
+ for (const k of SENSITIVE) assert.ok(!(k in v), `${k} must be redacted when unauthenticated`);
23
+ });
24
+
25
+ test('authenticated view adds the client-infra inventory', () => {
26
+ const v = fullCapabilities(MANIFEST, STATUS, 42);
27
+ assert.equal(v.authenticated, true);
28
+ for (const k of SENSITIVE) assert.ok(k in v, `${k} must be present when authenticated`);
29
+ assert.deepEqual(v.toolchains, ['git', 'python3']);
30
+ assert.equal(v.container_runtime, 'docker');
31
+ // health still present
32
+ assert.equal(v.mode, 'SPLIT_PLANE');
33
+ assert.equal(v.contract.ok, false);
34
+ });
35
+
36
+ test('isAuthorized: correct bearer passes, wrong/absent fails', () => {
37
+ const tok = 'AGENT_API_KEY_123';
38
+ assert.equal(isAuthorized({ headers: { authorization: `Bearer ${tok}` } }, tok), true);
39
+ assert.equal(isAuthorized({ headers: { authorization: 'Bearer wrong' } }, tok), false);
40
+ assert.equal(isAuthorized({ headers: {} }, tok), false);
41
+ assert.equal(isAuthorized({ headers: { authorization: tok } }, tok), false); // missing "Bearer "
42
+ });
43
+
44
+ test('isAuthorized fails closed when no token is configured', () => {
45
+ assert.equal(isAuthorized({ headers: { authorization: 'Bearer anything' } }, null), false);
46
+ assert.equal(isAuthorized({ headers: { authorization: 'Bearer anything' } }, ''), false);
47
+ });
@@ -0,0 +1,87 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { buildManifest, deriveMode, computeCapabilityManifest } from '../src/siblings/capabilities.js';
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const loadFixture = (name) =>
10
+ JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures', 'capabilities', name), 'utf-8'));
11
+
12
+ test('pull-only Docker host derives SPLIT_PLANE, not FULL_LOCAL', () => {
13
+ const fx = loadFixture('pull-only-docker.json');
14
+ const m = buildManifest(fx);
15
+
16
+ assert.equal(m.mode, 'SPLIT_PLANE');
17
+ assert.notEqual(m.mode, 'FULL_LOCAL');
18
+ // Evidence the fixture was interpreted as intended:
19
+ assert.equal(m.source_available, false);
20
+ assert.equal(m.build_capable, false);
21
+ assert.equal(m.test_capable, false);
22
+ assert.deepEqual(m.toolchains.sort(), ['git', 'python3']);
23
+ assert.match(m.reason, /no source working tree/);
24
+ });
25
+
26
+ test('full toolchain + source on disk derives FULL_LOCAL', () => {
27
+ const m = buildManifest({
28
+ diag: { tools: {
29
+ git: { installed: true }, java: { installed: true }, maven: { installed: true },
30
+ node: { installed: true }, npm: { installed: true },
31
+ } },
32
+ sourceAvailable: true,
33
+ egress: { gateway: true },
34
+ conclusive: true,
35
+ });
36
+ assert.equal(m.mode, 'FULL_LOCAL');
37
+ assert.equal(m.build_capable, true);
38
+ assert.equal(m.test_capable, true);
39
+ });
40
+
41
+ test('inconclusive detection NEVER defaults to FULL_LOCAL', () => {
42
+ const m = buildManifest({
43
+ diag: { tools: { java: { installed: true }, maven: { installed: true }, git: { installed: true } } },
44
+ sourceAvailable: true, // even with "everything", inconclusive must degrade
45
+ egress: { gateway: true },
46
+ conclusive: false,
47
+ });
48
+ assert.equal(m.mode, 'DETECT_ONLY');
49
+ assert.match(m.reason, /inconclusive/);
50
+ });
51
+
52
+ test('no source, no toolchain, no git/egress derives DETECT_ONLY', () => {
53
+ const { mode } = deriveMode({
54
+ source_available: false, build_capable: false, test_capable: false,
55
+ hasGit: false, egress: {}, conclusive: true,
56
+ });
57
+ assert.equal(mode, 'DETECT_ONLY');
58
+ });
59
+
60
+ test('computeCapabilityManifest degrades to DETECT_ONLY when diagnostics throw', async () => {
61
+ const throwingDiagnostics = { diagnose: async () => { throw new Error('boom'); } };
62
+ const m = await computeCapabilityManifest({
63
+ diagnostics: throwingDiagnostics,
64
+ runtimeRegistry: { runtimes: new Map() },
65
+ probes: { sourceAvailable: true, dockerSock: false, containerRuntime: 'docker', egress: { gateway: true } },
66
+ });
67
+ assert.equal(m.mode, 'DETECT_ONLY');
68
+ assert.match(m.reason, /inconclusive/);
69
+ });
70
+
71
+ test('computeCapabilityManifest honours injected probes + diagnostics (pull-only)', async () => {
72
+ const fx = loadFixture('pull-only-docker.json');
73
+ const diagnostics = { diagnose: async () => fx.diag };
74
+ const m = await computeCapabilityManifest({
75
+ diagnostics,
76
+ runtimeRegistry: { runtimes: new Map() },
77
+ probes: {
78
+ sourceAvailable: fx.sourceAvailable,
79
+ dockerSock: fx.dockerSock,
80
+ containerRuntime: fx.containerRuntime,
81
+ egress: fx.egress,
82
+ },
83
+ });
84
+ assert.equal(m.mode, 'SPLIT_PLANE');
85
+ assert.equal(m.container_runtime, 'docker');
86
+ assert.equal(m.docker_sock, false);
87
+ });
@@ -0,0 +1,23 @@
1
+ // test/child-env.test.js — model-authored commands must not see the agent's secrets.
2
+ import { test, describe } from 'node:test';
3
+ import assert from 'node:assert/strict';
4
+ import { sanitizedChildEnv, isSecretEnvName } from '../src/utils/child-env.js';
5
+
6
+ describe('sanitizedChildEnv', () => {
7
+ test('strips token/secret/password/key variables, keeps the rest and extras', () => {
8
+ const env = {
9
+ PATH: '/usr/bin', HOME: '/home/x', JAVA_HOME: '/opt/java',
10
+ AUTH_TOKEN: 'a', DEEPDEBUG_API_KEY: 'b', AGENT_SHARED_SECRET: 'c', LOCAL_AGENT_TOKEN: 'd',
11
+ GITHUB_TOKEN: 'e', DB_PASSWORD: 'f', AWS_SECRET_ACCESS_KEY: 'g', LOCAL_AGENT_SIGNING_KEY: 'h',
12
+ GIT_TERMINAL_PROMPT: '0', GIT_ASKPASS: 'echo', UNDEFINED: undefined,
13
+ };
14
+ const out = sanitizedChildEnv(env, { FORCE_COLOR: '0' });
15
+ assert.deepEqual(out, { PATH: '/usr/bin', HOME: '/home/x', JAVA_HOME: '/opt/java', GIT_TERMINAL_PROMPT: '0', GIT_ASKPASS: 'echo', FORCE_COLOR: '0' });
16
+ });
17
+ test('isSecretEnvName', () => {
18
+ assert.equal(isSecretEnvName('NPM_TOKEN'), true);
19
+ assert.equal(isSecretEnvName('npm_config_registry'), false);
20
+ assert.equal(isSecretEnvName('GIT_SSH_COMMAND'), false);
21
+ assert.equal(isSecretEnvName('MAVEN_OPTS'), false);
22
+ });
23
+ });
@@ -0,0 +1,71 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createRequire } from 'node:module';
4
+
5
+ const require = createRequire(import.meta.url);
6
+ const { applyEnvCompat, LEGACY_MAP } = require('../src/siblings/env-compat.cjs');
7
+
8
+ test('legacy fallback works when canonical is unset', () => {
9
+ const env = { DEEPDEBUG_API_URL: 'https://gw' };
10
+ const warnings = [];
11
+ const mapped = applyEnvCompat(env, (m) => warnings.push(m));
12
+ assert.equal(env.GATEWAY_URL, 'https://gw');
13
+ assert.deepEqual(mapped, ['GATEWAY_URL']);
14
+ });
15
+
16
+ test('canonical wins when both are set (no mapping, no warning)', () => {
17
+ const env = { GATEWAY_URL: 'https://canonical', DEEPDEBUG_API_URL: 'https://legacy' };
18
+ const warnings = [];
19
+ const mapped = applyEnvCompat(env, (m) => warnings.push(m));
20
+ assert.equal(env.GATEWAY_URL, 'https://canonical');
21
+ assert.equal(mapped.length, 0);
22
+ assert.equal(warnings.length, 0);
23
+ });
24
+
25
+ test('warning is emitted exactly once per mapped legacy var, naming it', () => {
26
+ const env = { DEEPDEBUG_API_URL: 'https://gw' };
27
+ const warnings = [];
28
+ applyEnvCompat(env, (m) => warnings.push(m));
29
+ assert.equal(warnings.length, 1);
30
+ assert.match(warnings[0], /DEEPDEBUG_API_URL/);
31
+ assert.match(warnings[0], /GATEWAY_URL/);
32
+ assert.match(warnings[0], /DEPRECATION/);
33
+ });
34
+
35
+ test('neither set is handled (no mapping, no warning)', () => {
36
+ const env = {};
37
+ const warnings = [];
38
+ const mapped = applyEnvCompat(env, (m) => warnings.push(m));
39
+ assert.equal(mapped.length, 0);
40
+ assert.equal(warnings.length, 0);
41
+ assert.equal(env.GATEWAY_URL, undefined);
42
+ });
43
+
44
+ test('empty-string canonical is treated as unset (legacy fills it)', () => {
45
+ const env = { TENANT_ID: '', DEEPDEBUG_TENANT_ID: 't-123' };
46
+ applyEnvCompat(env, () => {});
47
+ assert.equal(env.TENANT_ID, 't-123');
48
+ });
49
+
50
+ test('all three mappings apply independently, one warning each', () => {
51
+ const env = {
52
+ DEEPDEBUG_API_URL: 'https://gw',
53
+ DEEPDEBUG_TENANT_ID: 't-1',
54
+ DEEPDEBUG_WORKSPACE_PATH: '/workspace',
55
+ };
56
+ const warnings = [];
57
+ const mapped = applyEnvCompat(env, (m) => warnings.push(m));
58
+ assert.deepEqual(mapped.sort(), ['GATEWAY_URL', 'TENANT_ID', 'WORKSPACE_ROOT']);
59
+ assert.equal(warnings.length, 3);
60
+ assert.equal(env.GATEWAY_URL, 'https://gw');
61
+ assert.equal(env.TENANT_ID, 't-1');
62
+ assert.equal(env.WORKSPACE_ROOT, '/workspace');
63
+ });
64
+
65
+ test('LEGACY_MAP is the canonical 3-pair mapping', () => {
66
+ assert.deepEqual(LEGACY_MAP, [
67
+ ['DEEPDEBUG_API_URL', 'GATEWAY_URL'],
68
+ ['DEEPDEBUG_TENANT_ID', 'TENANT_ID'],
69
+ ['DEEPDEBUG_WORKSPACE_PATH', 'WORKSPACE_ROOT'],
70
+ ]);
71
+ });